文:徐超,《React进阶之路》做者html
受权发布,转载请注明做者及出处前端
React 深刻系列,深刻讲解了React中的重点概念、特性和模式等,旨在帮助你们加深对React的理解,以及在项目中更加灵活地使用React。git
React 的核心思想是组件化的思想,而React 组件的定义能够经过下面的公式描述:github
UI = Component(props, state)
复制代码
组件根据props和state两个参数,计算获得对应界面的UI。可见,props 和 state 是组件的两个重要数据源。redux
本篇文章不是对props 和state 基本用法的介绍,而是尝试从更深层次解释props 和 state,而且概括使用它们时的注意事项。数组
**一句话归纳,props 是组件对外的接口,state 是组件对内的接口。**组件内能够引用其余组件,组件之间的引用造成了一个树状结构(组件树),若是下层组件须要使用上层组件的数据或方法,上层组件就能够经过下层组件的props属性进行传递,所以props是组件对外的接口。组件除了使用上层组件传递的数据外,自身也可能须要维护管理数据,这就是组件对内的接口state。根据对外接口props 和对内接口state,组件计算出对应界面的UI。bash
组件的props 和 state都和组件最终渲染出的UI直接相关。二者的主要区别是:state是可变的,是组件内部维护的一组用于反映组件UI变化的状态集合;而props是组件的只读属性,组件内部不能直接修改props,要想修改props,只能在该组件的上层组件中修改。在组件状态上移的场景中,父组件正是经过子组件的props,传递给子组件其所须要的状态。markdown
定义一个合适的state,是正确建立组件的第一步。state必须能表明一个组件UI呈现的完整状态集,即组件对应UI的任何改变,均可以从state的变化中反映出来;同时,state还必须是表明一个组件UI呈现的最小状态集,即state中的全部状态都是用于反映组件UI的变化,没有任何多余的状态,也不须要经过其余状态计算而来的中间状态。前端工程师
组件中用到的一个变量是否是应该做为组件state,能够经过下面的4条依据进行判断:ecmascript
请务必牢记,并非组件中用到的全部变量都是组件的状态!当存在多个组件共同依赖同一个状态时,通常的作法是状态上移,将这个状态放到这几个组件的公共父组件中。
直接修改state,组件并不会从新重发render。例如:
// 错误 this.state.title = 'React'; 复制代码
正确的修改方式是使用setState()
:
// 正确 this.setState({title: 'React'}); 复制代码
调用setState
,组件的state并不会当即改变,setState
只是把要修改的状态放入一个队列中,React会优化真正的执行时机,而且React会出于性能缘由,可能会将屡次setState
的状态修改合并成一次状态修改。因此不能依赖当前的state,计算下个state。当真正执行状态修改时,依赖的this.state并不能保证是最新的state,由于React会把屡次state的修改合并成一次,这时,this.state仍是等于这几回修改发生前的state。另外须要注意的是,一样不能依赖当前的props计算下个state,由于props的更新也是异步的。
举个例子,对于一个电商类应用,在咱们的购物车中,当点击一次购买按钮,购买的数量就会加1,若是咱们连续点击了两次按钮,就会连续调用两次this.setState({quantity: this.state.quantity + 1})
,在React合并屡次修改成一次的状况下,至关于等价执行了以下代码:
Object.assign(
previousState,
{quantity: this.state.quantity + 1},
{quantity: this.state.quantity + 1}
)
复制代码
因而乎,后面的操做覆盖掉了前面的操做,最终购买的数量只增长了1个。
若是你真的有这样的需求,可使用另外一个接收一个函数做为参数的setState
,这个函数有两个参数,第一个参数是组件的前一个state(本次组件状态修改为功前的state),第二个参数是组件当前最新的props。以下所示:
// 正确
this.setState((preState, props) => ({
counter: preState.quantity + 1;
}))
复制代码
当调用setState
修改组件状态时,只须要传入发生改变的状态变量,而不是组件完整的state,由于组件state的更新是一个浅合并(Shallow Merge)的过程。例如,一个组件的state为:
this.state = { title : 'React', content : 'React is an wonderful JS library!' } 复制代码
当只须要修改状态title
时,只须要将修改后的title
传给setState
:
this.setState({title: 'Reactjs'}); 复制代码
React会合并新的title
到原来的组件state中,同时保留原有的状态content
,合并后的state为:
{ title : 'Reactjs', content : 'React is an wonderful JS library!' } 复制代码
React官方建议把state看成不可变对象,一方面是若是直接修改this.state,组件并不会从新render;另外一方面state中包含的全部状态都应该是不可变对象。当state中的某个状态发生变化,咱们应该从新建立一个新状态,而不是直接修改原来的状态。那么,当状态发生变化时,如何建立新的状态呢?根据状态的类型,能够分红三种状况:
这种状况最简单,由于状态是不可变类型,直接给要修改的状态赋一个新值便可。如要修改count(数字类型)、title(字符串类型)、success(布尔类型)三个状态:
this.setState({ count: 1, title: 'Redux', success: true }) 复制代码
若有一个数组类型的状态books,当向books中增长一本书时,使用数组的concat方法或ES6的数组扩展语法(spread syntax):
// 方法一:使用preState、concat建立新数组 this.setState(preState => ({ books: preState.books.concat(['React Guide']); })) // 方法二:ES6 spread syntax this.setState(preState => ({ books: [...preState.books, 'React Guide']; })) 复制代码
当从books中截取部分元素做为新状态时,使用数组的slice方法:
// 使用preState、slice建立新数组
this.setState(preState => ({
books: preState.books.slice(1,3);
}))
复制代码
当从books中过滤部分元素后,做为新状态时,使用数组的filter方法:
// 使用preState、filter建立新数组 this.setState(preState => ({ books: preState.books.filter(item => { return item != 'React'; }); })) 复制代码
注意不要使用push、pop、shift、unshift、splice等方法修改数组类型的状态,由于这些方法都是在原数组的基础上修改,而concat、slice、filter会返回一个新的数组。
如state中有一个状态owner,结构以下:
this.state = { owner = { name: '老干部', age: 30 } } 复制代码
当修改state时,有以下两种方式:
1) 使用ES6 的Object.assgin方法
this.setState(preState => ({ owner: Object.assign({}, preState.owner, {name: 'Jason'}); })) 复制代码
2) 使用对象扩展语法(object spread properties)
this.setState(preState => ({ owner: {...preState.owner, name: 'Jason'}; })) 复制代码
总结一下,建立新的状态的关键是,避免使用会直接修改原对象的方法,而是使用能够返回一个新对象的方法。固然,也可使用一些Immutable的JS库,如Immutable.js,实现相似的效果。
那么,为何React推荐组件的状态是不可变对象呢?一方面是由于不可变对象方便管理和调试,了解更多可参考这里;另外一方面是出于性能考虑,当组件状态都是不可变对象时,咱们在组件的shouldComponentUpdate
方法中,仅须要比较状态的引用就能够判断状态是否真的改变,从而避免没必要要的render
方法的调用。当咱们使用React 提供的PureComponent
时,更是要保证组件状态是不可变对象,不然在组件的shouldComponentUpdate
方法中,状态比较就可能出现错误。
React 深刻系列4:组件的生命周期
新书推荐《React进阶之路》
做者:徐超
毕业于浙江大学,硕士,资深前端工程师,长期就任于能源物联网公司远景智能。8年软件开发经验,熟悉大前端技术,拥有丰富的Web前端和移动端开发经验,尤为对React技术栈和移动Hybrid开发技术有深刻的理解和实践经验。
2019年,iKcamp原创新书《Koa与Node.js开发实战》已在京东、天猫、亚马逊、当当开售啦!