React 深入系列3:Props 和 State
文:徐超,《React进阶之路》作者
授权发布,转载请注明作者及出处
React 深入系列3:Props 和 State
React 深入系列,深入讲解了React中的重点概念、特性和模式等,旨在帮助大家加深对React的理解,以及在项目中更加灵活地使用React。
React 的核心思想是组件化的思想,而React 组件的定义可以通过下面的公式描述:
UI = Component(props, state)
组件根据props和state两个参数,计算得到对应界面的UI。可见,props 和 state 是组件的两个重要数据源。
本篇文章不是对props 和state 基本用法的介绍,而是尝试从更深层次解释props 和 state,并且归纳使用它们时的注意事项。
Props 和 State 本质
一句话概括,props 是组件对外的接口,state 是组件对内的接口。组件内可以引用其他组件,组件之间的引用形成了一个树状结构(组件树),如果下层组件需要使用上层组件的数据或方法,上层组件就可以通过下层组件的props属性进行传递,因此props是组件对外的接口。组件除了使用上层组件传递的数据外,自身也可能需要维护管理数据,这就是组件对内的接口state。根据对外接口props 和对内接口state,组件计算出对应界面的UI。
组件的props 和 state都和组件最终渲染出的UI直接相关。两者的主要区别是:state是可变的,是组件内部维护的一组用于反映组件UI变化的状态集合;而props是组件的只读属性,组件内部不能直接修改props,要想修改props,只能在该组件的上层组件中修改。在组件状态上移的场景中,父组件正是通过子组件的props,传递给子组件其所需要的状态。
如何定义State
定义一个合适的state,是正确创建组件的第一步。state必须能代表一个组件UI呈现的完整状态集,即组件对应UI的任何改变,都可以从state的变化中反映出来;同时,state还必须是代表一个组件UI呈现的最小状态集,即state中的所有状态都是用于反映组件UI的变化,没有任何多余的状态,也不需要通过其他状态计算而来的中间状态。
组件中用到的一个变量是不是应该作为组件state,可以通过下面的4条依据进行判断:
- 这个变量是否是通过props从父组件中获取?如果是,那么它不是一个状态。
- 这个变量是否在组件的整个生命周期中都保持不变?如果是,那么它不是一个状态。
- 这个变量是否可以通过state 或props 中的已有数据计算得到?如果是,那么它不是一个状态。
- 这个变量是否在组件的render方法中使用?如果不是,那么它不是一个状态。这种情况下,这个变量更适合定义为组件的一个普通属性(除了props 和 state以外的组件属性 ),例如组件中用到的定时器,就应该直接定义为this.timer,而不是this.state.timer。
请务必牢记,并不是组件中用到的所有变量都是组件的状态!当存在多个组件共同依赖同一个状态时,一般的做法是状态上移,将这个状态放到这几个组件的公共父组件中。
如何正确修改State
1.不能直接修改State。
直接修改state,组件并不会重新重发render。例如:
// 错误
this.state.title = 'React';
正确的修改方式是使用setState()
:
// 正确
this.setState({title: 'React'});
2. State 的更新是异步的。
调用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;
}))
3. State 的更新是一个浅合并(Shallow Merge)的过程。
当调用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!'
}
State与Immutable
React官方建议把state当作不可变对象,一方面是如果直接修改this.state,组件并不会重新render;另一方面state中包含的所有状态都应该是不可变对象。当state中的某个状态发生变化,我们应该重新创建一个新状态,而不是直接修改原来的状态。那么,当状态发生变化时,如何创建新的状态呢?根据状态的类型,可以分成三种情况:
1. 状态的类型是不可变类型(数字,字符串,布尔值,null, undefined)
这种情况最简单,因为状态是不可变类型,直接给要修改的状态赋一个新值即可。如要修改count(数字类型)、title(字符串类型)、success(布尔类型)三个状态:
this.setState({
count: 1,
title: 'Redux',
success: true
})
2. 状态的类型是数组
如有一个数组类型的状态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会返回一个新的数组。
3. 状态的类型是简单对象(Plain Object)
如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\2020年前端实习生(偏动效方向)
有意者邮件:yao.zhou@meituan.com
React 深入系列3:Props 和 State的更多相关文章
- react native中对props和state的理解
最近使用react native这个新的技术做完一个项目,所以赶紧写个博客巩固一下. 今天我想说的是props和state,当然这是我个人的理解,如果有什么不对的地方,望指正. 首先我先说说props ...
- 从 0 到 1 实现 React 系列 —— 2.组件和 state|props
看源码一个痛处是会陷进理不顺主干的困局中,本系列文章在实现一个 (x)react 的同时理顺 React 框架的主干内容(JSX/虚拟DOM/组件/生命周期/diff算法/setState/ref/. ...
- React系列之--props属性
版权声明:本文为博主原创文章,未经博主允许不得转载. PS:转载请注明出处作者:TigerChain地址:http://www.jianshu.com/p/fa81cebac3ef本文出自TigerC ...
- React组件系统、props与状态(state)
多个组件合成一个组件: var style = { fontSize: 20, color: '#ff0000' }; var WebSite = React.createClass({ rende ...
- React Native基础&入门教程:以一个To Do List小例子,看props和state
本文由葡萄城技术团队于博客园原创并首发 转载请注明出处:葡萄城官网,葡萄城为开发者提供专业的开发工具.解决方案和服务,赋能开发者. 在上篇中,我们介绍了什么是Flexbox布局,以及如何使用Flexb ...
- React中props和state相同点和不同点
朋友们,我想死你们了,最近这几天忙着和病魔作斗争所以没怎么写博客,今天感觉好点了,赶紧来写一波,就是这木敬业. 今天我们来讨论讨论props和state相同点和不同点 首先我来概要说明一下这两者 pr ...
- React中Props 和 State用法
React中Props 和 State用法 1.本质 一句话概括,props 是组件对外的接口,state 是组件对内的接口.组件内可以引用其他组件,组件之间的引用形成了一个树状结构(组件树),如果下 ...
- React中props与state
以下内容均为个人理解. 1.state: 在react中,state可以看成管理页面状态的集合(实则一个对象而已),库里面的成员均为页面渲染变量,整个页面为一个状态机,当state发生变化时,页面会重 ...
- React Native 快速入门之认识Props和State
眼下React Native(以后简称RN)越来越火,我也要投入到学习当中.对于一个前端来说,还是有些难度.因为本人觉得这是一个App开发的领域,自然是不同.编写本文的时候,RN的版本为0.21.0. ...
随机推荐
- SpringBoot集成redis的key,value序列化的相关问题
使用的是maven工程 springBoot集成redis默认使用的是注解,在官方文档中只需要2步; 1.在pom文件中引入即可 <dependency> <groupId>o ...
- redis-cli的一些有趣也很有用的功能
redis-cli我们最常用的两个参数就是-h.-p.-a选项,分配用来指定连接的redis-server的host和port. 通过redis-cli –help发现,redis-cli还提供了其他 ...
- 【Java】 重拾Java入门
[概论与基本语法] 取这个标题,还是感觉有些大言不惭.之前大三的时候自学过一些基本的java知识,大概到了能独立写一个GUI出来的水平把,不过后来随着有了其他目标,就把这块放下了.之后常年没有用,早就 ...
- java排序算法(六):直接插入排序
java排序算法(六):直接插入排序 直接插入排序的基本操作就是将待的数据元素按其关键字的大小插入到前面的有序序列中 直接插入排序时间效率并不高,如果在最坏的情况下,所有元素的比较次数的总和为(0+1 ...
- 程序猿媛 九:Adroid zxing 二维码3.1集成(源码无删减)
Adroid zxing 二维码3.1集成 声明:博文为原创,文章内容为,效果展示,思路阐述,及代码片段. 转载请保留原文出处“http://my.oschina.net/gluoyer/blog”, ...
- <经验杂谈>介绍Js简单的递归排列组合
最近在开发SKU模块的时候,遇到这样一个需求,某种商品有N(用未知数N来表示是因为规格的数组由用户制定且随时可以编辑的,所以对程序来说,它是一个未知数)类规格,每一类规格又有M个规格值,各种规格值的组 ...
- java 中的JDK封装的数据结构和算法解析(集合类)----顺序表 List 之 ArrayList
1. 数据结构之List (java:接口)[由于是分析原理,这里多用截图说明] List是集合类中的容器之一,其定义如下:(无序可重复) An ordered collection (also kn ...
- JavaScript(第五天)【流程控制语句】
ECMA-262规定了一组流程控制语句.语句定义了ECMAScript中的主要语法,语句通常由一个或者多个关键字来完成给定的任务.诸如:判断.循环.退出等. 一.语句的定义 在ECMAScri ...
- 2018上C语言程序设计(高级)作业- 第1次作业
未来两周学习内容 复习指针的定义和引用 指针的应用场景: 指针作为函数参数(角色互换) 指针作为函数的参数返回多个值 指针.数组和地址间的关系 使用指针进行数组操作 数组名(指针)作为函数参数(冒泡排 ...
- c语音-第零次作业
1.你认为大学的学习生活.同学关系.师生应该是怎样? 我认为大学学习应该以自我学习为主,由以往的被动学习改为主动学习,探索新世界,除学习专业知识外对自身欠缺的地方也应该加以补足:同学之间要互相帮助,更 ...