React 入门
Learning from React.js 小书 安装react.js全家桶
使用JSX描述UI信息你会发现,HTML 的信息和 JavaScript 所包含的结构和信息其实是一样的,我们可以用 JavaScript 对象来描述所有能用 HTML 表示的 UI 信息。但是用 JavaScript 写起来太长了,结构看起来又不清晰,用 HTML 的方式写起来就方便很多了。 如 import React,{ Component } from 'react'
import ReactDOM from 'react-dom'
import './index.css'
class Header extends Component {
render () {
return (
<div>
<h1 className='title'>React 小书</h1>
</div>
)
}
}
ReactDOM.render(
<Header />,document.getElementById('root')
)
所以可以总结一下从 JSX 到页面到底经过了什么样的过程:
组件的Render方法React.js 中一切皆组件,用 React.js 写的其实就是 React.js 组件。在编写组件的时候,一般都要继承 import React,{ Component } from 'react';
一个组件类必须要实现一个render方法,这个render方法必须要返回一个JSX元素。需要注意的是,只能返回一个元素,不能返回并列的多个JSX元素,如以下是错误的: ...
render () {
return (
<div>第一个</div>
<div>第二个</div>
)
}
...
必须要用一个外层 表达式插入: 如 ...
render () {
const word = 'is good'
return (
<div>
<h1>React 小书 {word}</h1>
</div>
)
}
...
...
render () {
const isGoodWord = true
const goodWord = <strong> is good</strong> const badWord = <span> is not good</span> return ( <div> <h1> React 小书 {isGoodWord ? goodWord : badWord} </h1> </div> ) } ...
组件的组合、嵌套和组件树//组件的嵌套
class Title extends Component {
render () {
return (
<h1>React 小书</h1> ) } } class Header extends Component { render () { return ( <div> <Title /> </div> ) } }
这样的可复用性非常强。这里要注意的是,自定义的组件都必须要用大写字母开头,普通的 HTML 标签都用小写字母开头。 事件监听
案例:(添加绑定) import React,{ Component } from 'react';
class LikeButton extends Component{
constructor () {
super()
this.state = { liked: false }
}
handleClickOnLikeButton() {
this.setState({liked: !this.state.liked})
}
render(){
return (
<button onClick={this.handleClickOnLikeButton.bind(this)}> {this.state.liked ? '取消' : '点赞'} </button> ); }; } export default LikeButton;
这样的结果就是,用户每次点击, 也就是说,你只要调用 注意:(后面有提)
//我们来尝试一下,当用户点击`h1`的时候,把`h1`的`innerHTML`打印出来
class Title extends Component {
handleClickOnTitle (e) {
console.log(e.target.innerHTML)
}
render () {
return (
<h1 onClick={this.handleClickOnTitle}>React 小书</h1> ) } }
...
handleClickOnTitle (e) {
console.log(this) // => null or undefined
}
...
这是因为 React.js 调用你所传给它的方法的时候,并不是通过对象方法的方式调用(this.handleClickOnTitle),而是直接通过函数调用 (handleClickOnTitle),所以事件监听函数内并不能通过 this 获取到实例。 如果你想在事件函数当中使用当前的实例,你需要手动地将实例方法 bind 到当前实例上再传入给 React.js。 也可以在 bind 的时候给事件监听函数传入一些参数: class Title extends Component {
handleClickOnTitle (word,e) {
console.log(this,word)
}
render () {
return (
<h1 onClick={this.handleClickOnTitle.bind(this, 'Hello')}>React 小书</h1> ) } }
总结
组件的state和setState案例: class LikeButton extends Component {
constructor () {
super()
this.state = { isLiked: false }
}
handleClickOnLikeButton () {
this.setState({
isLiked: !this.state.isLiked
})
}
render () {
return (
<button onClick={this.handleClickOnLikeButton.bind(this)}>
{this.state.isLiked ? '取消' : '点赞'}
</button>
)
}
}
先在构造
...
handleClickOnLikeButton () {
this.setState({ count: 0 }) // => this.state.count 还是 undefined
this.setState({ count: this.state.count + 1}) // => undefined + 1 = NaN
this.setState({ count: this.state.count + 2}) // => NaN + 2 = NaN
}
...
这里就自然地引出了 setState 的第二种使用方式,可以接受一个函数作为参数。React.js 会把上一个 setState 的结果传入这个函数,你就可以使用该结果进行运算、操作,然后返回一个对象作为更新 state 的对象: ...
handleClickOnLikeButton () {
this.setState((prevState) => {
return { count: 0 }
})
this.setState((prevState) => {
return { count: prevState.count + 1 } // 上一个 setState 的返回是 count 为 0,当前返回 1
})
this.setState((prevState) => {
return { count: prevState.count + 2 } // 上一个 setState 的返回是 count 为 1,当前返回 3
})
// 最后的结果是 this.state.count 为 3
}
...
一道练习题“不能摸的狗(二)” class Dog extends Component {
constructor(){
super()
this.state = {
isRunning: false,isBarking: false
}
}
// 随机数(秒数)
getRandomArbitrary(min,max) {
return Math.floor(Math.random() * (max - min)) + min;
}
run(){
this.setState( {
isRunning: true,})
setTimeout(() => this.setState({ isRunning: false }),this.getRandomArbitrary(2000,5000)) //2~5秒,20~50毫秒眼花+_+
}
bark(){
this.setState( {
isBarking: true,})
setTimeout(() => this.setState({ isBarking: false }),20~50毫秒眼花+_+
}
handleDog(){
this.run()
this.bark()
}
render(){
return (
<div> <button onClick={this.handleDog.bind(this)}>HandleDog</button> <h5>{this.state.isRunning ? 'Dog is Running' : 'Dog is stoped'}</h5> <h5>{this.state.isBarking ? 'Dog is Barking' : 'Dog is stop barking'}</h5> </div> ) } }
配置组件的props定义: const likedText = this.props.likedText || '取消'
const unlikedText = this.props.unlikedText || '点赞'
可以用以下方式从JSX中传参数过去: <LikeButton likedText='已赞' unlikedText='赞' />
也可以把一个对象传给点赞组件作为参数: class Index extends Component {
render () {
return (
<div>
<LikeButton wordings={{likedText: '已赞',unlikedText: '赞'}} />
</div>
)
}
}
这里把 const wordings = this.props.wordings || {
likedText: '取消',unlikedText: '点赞'
}
//Index控件的定义
class Index extends Component {
render () {
return (
<div>
<LikeButton
wordings={{likedText: '已赞',unlikedText: '赞'}}
onClick={() => console.log('Click on like button!')}/>
</div>
)
}
}
解析:以上代码的 ...
handleClickOnLikeButton () {
this.setState({
isLiked: !this.state.isLiked
})
if (this.props.onClick) {
this.props.onClick()
}
}
...
当每次点击按钮的时候,控制台会显示
class LikeButton extends Component {
static defaultProps = {
likedText: '取消',unlikedText: '点赞'
}
...
}
使用了
...
handleClickOnLikeButton () {
this.props.likedText = '取消'//此处报错
this.setState({
isLiked: !this.state.isLiked
})
}
...
但是 //在Index组件中
class Index extends Component {
constructor () {
super()
this.state = {
likedText: '已赞',unlikedText: '赞'
}
}
handleClickOnChange () {
this.setState({
likedText: '取消',unlikedText: '点赞'
})
}
render () {
return (
<div>
<LikeButton
likedText={this.state.likedText}
unlikedText={this.state.unlikedText} />
<div>
<button onClick={this.handleClickOnChange.bind(this)}>
修改 wordings
</button>
</div>
</div>
)
}
}
这里,由于用户点赞,导致
渲染列表数据假如要渲染一个用户列表数据: const users = [
{ username: 'Jerry',age: 21,gender: 'male' },{ username: 'Tomy',age: 22,{ username: 'Lily',age: 19,gender: 'female' },{ username: 'Lucy',age: 20,gender: 'female' }
]
可以使用 class Index extends Component {
render () {
const usersElements = [] // 保存每个用户渲染以后 JSX 的数组
for (let user of users) {
usersElements.push( // 循环每个用户,构建 JSX,push 到数组中
<div>
<div>姓名:{user.username}</div>
<div>年龄:{user.age}</div>
<div>性别:{user.gender}</div>
<hr />
</div>
)
}
return (
<div>{usersElements}</div>
)
}
}
这里用了一个新的数组 优化: class Index extends Component {
render () {
return (
<div>
{users.map((user) => {
return (
<div>
<div>姓名:{user.username}</div>
<div>年龄:{user.age}</div>
<div>性别:{user.gender}</div>
<hr />
</div>
)
})}
</div>
)
}
}
继续优化,进一步把渲染单独一个用户的结构抽离出来作为一个单独的组件: class User extends Component{
render(){
const {user} = this.props
//这段代码你可以认为是这样:
//const user = this.props.user;
//这是 ES6 的简写形式。
return (
<div>
<div>name: {user.username}</div>
<div>age: {user.age}</div>
<div>gender: {user.gender}</div>
<hr/>
</div>
)
}
}
class UserGroup extends Component{
render() {
return (
<div>
{users.map(user => <User user={user}/>)}
</div>
)
}
}
key为了让DOM高效运行,对于用表达式套数组罗列到页面上的元素,都要为每个元素加上 key 属性,这个 key 必须是每个元素唯一的标识。一般来说,key 的值可以直接后台数据返回的 id,因为后台的 id 都是唯一的。可以把上面的代码改成这样: class Index extends Component {
render () {
return (
<div>
{users.map((user,i) => <User key={i} user={user} />)}
</div>
)
}
}
前端应用状态管理 —— 状态提升我们将这种组件之间共享的状态交给组件最近的公共父节点保管,然后通过 props 把状态传递给子组件,这样就可以在组件之间共享数据了。 总结一下:当某个状态被多个组件依赖或者影响的时候,就把该状态提升到这些组件的最近公共父组件中去管理,用 props 传递数据或者函数来管理这种依赖或着影响的行为。 你会发现这种无限制的提升不是一个好的解决方案。一旦发生了提升,你就需要修改原来保存这个状态的组件的代码,也要把整个数据传递路径经过的组件都修改一遍,好让数据能够一层层地传递下去。这样对代码的组织管理维护带来很大的问题。到这里你可以抽象一下问题:
挂载阶段的组件生命周期这一节我们来讨论一下对于一个组件来说,
...
componentWillMount () {
ajax.get('http://json-api.com/user',(userData) => {
this.setState({ userData })
})
}
...
修改这个 Index 让这个时钟可以隐藏或者显示: class Index extends Component {
constructor () {
super()
this.state = { isShowClock: true }
}
handleShowOrHide () {
this.setState({
isShowClock: !this.state.isShowClock
})
}
render () {
return (
<div>
{this.state.isShowClock ? <Clock /> : null }
<button onClick={this.handleShowOrHide.bind(this)}>
显示或隐藏时钟
</button>
</div>
)
}
}
这里,因为隐藏时钟的时候,并没有清楚定时器,所以它被隐藏了之后还在不停地尝试
...
componentWillUnmount () {
clearInterval(this.timer)
}
...
总结 说一下本节没有提到的 更新阶段的组件生命周期这里为了知识的完整,补充关于更新阶段的组件生命周期:
ref 和 React.js 中的 DOM 操作React.js 并不能完全满足所有 DOM 操作需求,有些时候我们还是需要和 DOM 打交道。比如说你想进入页面以后自动 focus 到某个输入框,你需要调用 React.js 当中提供了 class AutoFocusInput extends Component {
componentDidMount () {
this.input.focus()
}
render () {
return (
<input ref={(input) => this.input = input} />
)
}
}
ReactDOM.render(
<AutoFocusInput />,document.getElementById('root')
)
这里我们给 然后我们就可以在 我们可以给任意代表 HTML 元素标签加上 ref 从而获取到它 DOM 元素然后调用 DOM API。但是记住一个原则:能不用 ref 就不用。 练习: //完成 Post 组件,接受一个字符串的 content 作为 props,Post 会把它显示到自己的 <p> 元素内。并且,点击 <p> 元素的时候,会使用 console.log 把元素的高度打印出来。
class Post extends Component {
render () {
return (
<p ref={(p) => this.p = p }
onClick={() => console.log(this.p.clientHeight)}>{this.props.content}</p>
)
}
}
props.children 和容器类组件目标,能写成以下代码的形式:(其中 ReactDOM.render(
<Card>
<h2>React.js 小书</h2>
<div>开源、免费、专业、简单</div>
订阅:<input />
</Card>,document.getElementById('root')
)
实际上,React.js 默认就支持这种写法,所有嵌套在组件中的 JSX 结构都可以在组件内部通过 class Card extends Component {
render () {
return (
<div className='card'>
<div className='card-content'>
{this.props.children}
//注意这句话
</div>
</div>
)
}
}
把 props.children 打印出来,你可以看到它其实是个数组: 我们甚至可以在组件内部把数组中的 JSX 元素安置在不同的地方: class Layout extends Component {
render () {
return (
<div className='two-cols-layout'>
<div className='sidebar'>
{this.props.children[0]}
</div>
<div className='main'>
{this.props.children[1]}
</div>
</div>
)
}
}
这是一个两列布局组件,嵌套的 JSX 的第一个结构会成为侧边栏,第二个结构会成为内容栏,其余的结构都会被忽略。这样通过这个布局组件,就可以在各个地方高度复用我们的布局。 dangerouslySetHTML 和 style 属性(to be continued.) PropTypes和组件参数验证为了避免JavaScript弱类型带来的缺陷,这里引入一个Proptypes参数验证,即可以验证是否已传入所需要的参数,传入的参数是否是目标类型,等等。 我们这里先安装一个 React 提供的第三方库 prop-types: import React,{ Component } from 'react'
import PropTypes from 'prop-types'
class Comment extends Component {
static propTypes = {
comment: PropTypes.object
//这里验证传入的comment是否是object类型
}
render () {
const { comment } = this.props
return (
<div className='comment'>
<div className='comment-user'>
<span>{comment.username} </span>:
</div>
<p>{comment.content}</p>
</div>
)
}
}
另外,为了避免所需的props没有被初始化,可以加一个默认值要求: ...
static propTypes = {
comment: PropTypes.object.isRequired
}
...
React.js 提供的 PropTypes 提供了一系列的数据类型可以用来配置组件的参数: PropTypes.array
PropTypes.bool
PropTypes.func
PropTypes.number
PropTypes.object
PropTypes.string
PropTypes.node
PropTypes.element
...
总结 高阶组件(Higher-Order Components)高阶组件就是一个函数,传给它一个组件,它返回一个新的组件。 const NewComponent = higherOrderComponent(OldComponent)
所以,注意,高阶组件是一个函数,而不是组件。我们看看一个很简单的高阶组件: import React,{ Component } from 'react'
export default (WrappedComponent) => {
class NewComponent extends Component {
// 可以做很多自定义逻辑
render () {
return <WrappedComponent />
}
}
return NewComponent
}
现在看来好像什么用都没有,它就是简单的构建了一个新的组件类 NewComponent,然后把传进入去的 WrappedComponent 渲染出来。但是我们可以给 NewCompoent 做一些数据启动工作: import React,{ Component } from 'react'
export default (WrappedComponent,name) => {
//这函数内定义了一个新组件`NewComponent`
class NewComponent extends Component {
constructor () {
super()
this.state = { data: null }
}
componentWillMount () {
let data = localStorage.getItem(name)
//根据函数的第二个参数`name`在挂在阶段从LocalStorage加载数据,并且setState到自己的state.data中。而render的时候再把`this.state.data`通过props.data传给WrappedComponent。
this.setState({ data })
}
render () {
return <WrappedComponent data={this.state.data} />
}
}
return NewComponent
}
这个高阶组件有什么用呢? import wrapWithLoadData from './wrapWithLoadData'
class InputWithUserName extends Component {
render () {
return <input value={this.props.data} />
}
}
InputWithUserName = wrapWithLoadData(InputWithUserName,'username')
//注意这里的用法
export default InputWithUserName
假如 InputWithUserName 的功能需求是挂载的时候从 LocalStorage 里面加载 username 字段作为 只需要定义一个非常简单的 InputWithUserName,它会把 props.data 作为 别人用这个组件的时候实际是用了被加工过的组件: import InputWithUserName from './InputWithUserName'
class Index extends Component {
render () {
return (
<div>
用户名:<InputWithUserName />
</div>
)
}
}
高阶组件的灵活性 React.js的contextReact.js 的 context 就是这么一个东西,某个组件只要往自己的 context 里面放了某些状态,这个组件之下的所有子组件都直接访问这个状态而不需要通过中间组件的传递。一个组件的 context 只有它的子组件能够访问,它的父组件是不能访问到的,你可以理解每个组件的 context 就是瀑布的源头,只能往下流不能往上飞。 我们在父组件里定义一个context: class Index extends Component {
static childContextTypes = {
themeColor: PropTypes.string
}
constructor () {
super()
this.state = { themeColor: 'red' }
}
getChildContext () {
return { themeColor: this.state.themeColor }
}
...
}
然后修改子组件,使其能获取这个状态: class Title extends Component {
static contextTypes = {
themeColor: PropTypes.string
}
render () {
return (
<h1 style={{ color: this.context.themeColor }}>React.js 小书标题</h1>
)
}
}
子组件要获取 context 里面的内容的话,就必须写 contextTypes 来声明和验证你需要获取的状态的类型,它也是必写的,如果你不写就无法获取 context 里面的状态。Title 想获取 themeColor,它是一个字符串,我们就在 contextTypes 里面进行声明。 接下来就开始 |