想要理解数据是如何在组件树内自上往下流动(Data Flow
),须要先明白React
组件的组合、嵌套和组件树的构成。html
注意:自定义的组件都必需要用大写字母开头,普通的
HTML
标签都用小写字母开头。react
下面是用React.js
组建的房子为例子来讲明一下组件的组和应用。一个房子里面有一个房间和一个厕所。房间里有一台电脑和两条狗,以下:git
// 电脑
class Computer extends Component { render () { return <h3>this is Computer</h3> } } // 狗 class Dog extends Component { render () { return <h3>this is Dog</h3> } } // 房间 class Room extends Component { render () { return ( <div> <h2>this is Room</h2> <Computer /> <Dog /> <Dog /> </div> ) } } // 厕所 class WC extends Component { render () { return <h2>this is WC</h2> } } // 房子 class House extends Component { render () { return <div> <h1>this is House</h1> <Room /> <WC /> </div> } } 复制代码
最后页面会输出以下图:github
组件能够和组件组合在一块儿,组件内部可使用别的组件。这样的组合嵌套,最后构成一个所谓的组件树。用下图表示这种树状结构它们之间的关系:web
state
state
是组件的当前状态,React
根据状态state
呈现不一样的UI
展现。React.js
的state
就是用来存储这种可变化的状态的。一旦状态(数据)更改,组件就会自动调用render
从新渲染 UI
,这个更改的动做经过this.setState()
方法来触发。数组
下面是一个简单的修改状态的小例子:异步
class Like extends Component {
constructor(props) { super(props) this.state = { isLike: false } } handleClick () { this.setState({ isLike: !this.state.isLike }) } render () { return ( <h3 onClick={this.handleClick.bind(this)}> 你{this.state.isLike ? '喜欢' : '不喜欢'}她 </h3> ) } } 复制代码
isLike
存放在实例的state
对象当中,这个对象在构造函数里面初始化。在这个组件的render
函数内,会根据组件的state
的中的isLike
的不一样显示喜欢或不喜欢。setState()
方法由父类Component
所提供。编辑器
state
和setState()
方法setState()
方法,它接受一个对象或者函数做为参数。函数
handleClick () {
// 只须要传入须要更新的部分,而不须要入整个对象。 this.setState({ isLike: !this.state.isLike }) } 复制代码
handleClick () {
this.setState(() => { return { isLike: !this.state.isLike } }) } 复制代码
state
不能直接修改state
,若是直接修改state
,组件并不会从新触发render
方法。性能
// 错误
this.state.isLike = !this.state.isLike // 正确 this.setState({ isLike: !this.state.isLike }) 复制代码
state
的更新是异步的在调用setState()
的时候React.js
并不会立刻修改state
。setState()
只是把要修改的状态放入一个更新队列中。React会优化真正的执行时机。以下:
class AddCount extends Component {
constructor(props) { super(props) this.state = { count: 0 } } handleClick () { this.setState({ count: this.state.count + 1 }) // 0 console.log(this.state.count) } render () { return ( <div> {/* 1 */} <h2>{this.state.count}</h2> <button onClick={this.handleClick.bind(this)}>添加</button> </div> ) } } 复制代码
当点击添加按钮的时候页面输出的是1
,打印出来的倒是0
。因此在调用setState()
以后,this.state
不会当即映射为新的值。
解决方案是setState(updater, [callback])
接收更新后能够传入一个回调函数,一旦setState()
完成而且组件重绘以后,这个回调函数将会被调用。
handleClick () {
this.setState({ count: this.state.count + 1 }, () => { // 1 console.log(this.state.count) }) } 复制代码
setState()
浅合并React.js
出于性能缘由,可能会将屡次setState()
的状态修改合并成一次状态修改。因此不要依赖当前的setState()
计算下个State
。以下的一个计数器:
class AddCount extends Component {
constructor(props) { super(props) this.state = { count: 0 } } handleClick () { this.setState({ count: this.state.count + 1 }) this.setState({ count: this.state.count + 1 }) } render () { return ( <div> <h2>{this.state.count}</h2> <button onClick={this.handleClick.bind(this)}>添加</button> </div> ) } } 复制代码
当点击添加按钮的时候,会发现页面输出的是1
。虽然咱们setState()
方法调用了两次。是由于当咱们调用setState()
修改组件状态时,组件state
的更新实际上是一个浅合并的过程,至关于:
Object.assign(
previousState, {count: state.count + 1}, {count: state.count + 1}, ... ) 复制代码
因此若是后续操做要依赖前一个setState()
的结果的状况下就要使用函数来做为setState()
参数。React.js
会把上一个setState()
的结果传入这个函数,你就可使用上一个正确的结果进行操做,而后返回一个对象来更新state。
handleClick () {
this.setState({ count: this.state.count + 1 }) this.setState((prevState) => { // 1 console.log(prevState.count) return { count: prevState.count + 1 } }) }} 复制代码
把上次操做setState()
更新的值传入到下一个setState()
里,就能够正确的显示count
了。
state
的Immutable
(不可变性)React
官方建议把state
看成是的Immutable
(不可变性)对象,state
中包含的全部状态都应该是不可变对象。当state
中的某个状态发生变化,咱们应该从新建立这个状态对象,而不是直接修改原来的状态。
关于
Immutable
能够参考Immutable详解及React中实践。
names
,当向
name
中添加一个名字时,使用数组的
concat
方法或
ES6
的扩展运算符:
// 1
this.setState(prevState => ({ names: prevState.names.concat(['lisi']) })) // 2 this.setState(prevState => ({ names: [...prevState.names,'lisi'] })) 复制代码
注意不要使用
push
、pop
、shift
、unshift
、splice
等方法修改数组类型的状态,由于这些方法都是在原数组的基础上修改,而concat
、slice
等返回一个新的数组。
person
,为了避免改变本来的对象,咱们可使用
Object.assign
方法或者对象扩展属性:
// 1
function updatePerson (person) { return Object.assign({}, person, { age: 20 }) } // 2 function updatePerson (person) { return {...person,age:20} } 复制代码
建立新的状态对象要避免使用会直接修改原对象的方法,而是使用能够返回一个新对象的方法。
当处理深层嵌套对象时,以immutable
(不可变)的方式更新会很麻烦。可使用一些Immutable
的JS库,如Immutable.js
来简化开发。
props
组件是相互独立、可复用的单元,一个组件可能在不一样地方被用到。可是在不一样的场景下对这个组件的需求可能会根据状况有所不一样,因此要针对相同的组件传入不一样的配置项。
React.js
的props
就能够帮助咱们达到这个效果。每一个组件均可以接受一个props
参数,它是一个对象,包含了全部你对这个组件的配置。
下面是一个能够经过传入props
来控制是否是能够点击修改喜欢或不喜欢的文字:
class Like extends Component {
constructor(props) { super(props) this.state = { isLike: false } } handleClick () { this.setState({ isLike: !this.state.isLike }) } render () { const clickable = this.props.clickable return ( <h3 onClick={clickable ? this.handleClick.bind(this) : () => { }} > 你{ this.state.isLike ? '喜欢' : '不喜欢'}她 </h3> ) } } class App extends Component { render () { return ( <div> <Like clickable={true} /> <Like clickable={false} /> </div> ) } } 复制代码
组件内部是经过this.props
的方式获取到组件的参数的,若是this.props
里面有须要的属性咱们就采用相应的属性,没有的话就用默认的属性。在使用一个组件的时候,能够把参数放在标签的属性当中,全部的属性都会做为props`对象的键值。
defaultProps
React.js
也提供了一种方式defaultProps
,能够方便的作到默认配置。
class Like extends Component {
static defaultProps = { clickable: true } //... } 复制代码
props
一旦传入进来就不能够在组件内部对它进行修改。可是能够经过父组件主动从新渲染的方式来传入新的props
,从而达到更新的效果。以下:
class App extends Component {
constructor(props) { super(props) this.state = { clickable: false } } handleClickOnChange () { this.setState({ clickable: !this.state.clickable }) } render () { return ( <div> <Like clickable={this.state.clickable} /> <Like clickable={this.state.clickable} /> <button onClick={this.handleClickOnChange.bind(this)}> 修改clickable </button> </div> ) } 复制代码
https://zh-hans.reactjs.org/
https://juejin.im/entry/59522bdb6fb9a06b9a516113
https://github.com/camsong/blog/issues/3
https://github.com/lcxfs1991/blog/issues/8
http://huziketang.mangojuice.top/books/react/
https://dev.to/rleija_/master-the-art-of-react-state-and-props-in-5-minutes-3hao
http://product.dangdang.com/25249546.html
本文使用 mdnice 排版