[转] 深入理解React 组件状态(State)

React 的核心思想是组件化的思想,应用由组件搭建而成,而组件中最重要的概念是State(状态),State是一个组件的UI数据模型,是组件渲染时的数据依据。

一. 如何定义State

定义一个合适的State,是正确创建组件的第一步。State必须能代表一个组件UI呈现的完整状态集,即组件的任何UI改变,都可以从State的变化中反映出来;同时,State还必须是代表一个组件UI呈现的最小状态集,即State中的所有状态都是用于反映组件UI的变化,没有任何多余的状态,也不需要通过其他状态计算而来的中间状态。

组件中用到的一个变量是不是应该作为组件State,可以通过下面的4条依据进行判断:

  1. 这个变量是否是通过Props从父组件中获取?如果是,那么它不是一个状态。
  2. 这个变量是否在组件的整个生命周期中都保持不变?如果是,那么它不是一个状态。
  3. 这个变量是否可以通过其他状态(State)或者属性(Props)计算得到?如果是,那么它不是一个状态。
  4. 这个变量是否在组件的render方法中使用?如果不是,那么它不是一个状态。这种情况下,这个变量更适合定义为组件的一个普通属性,例如组件中用到的定时器,就应该直接定义为this.timer,而不是this.state.timer。

请务必牢记,并不是组件中用到的所有变量都是组件的状态!当存在多个组件共同依赖一个状态时,一般的做法是状态上移,将这个状态放到这几个组件的公共父组件中。

二. State 与 Props 区别

除了State, 组件的Props也是和组件的UI有关的。他们之间的主要区别是:State是可变的,是组件内部维护的一组用于反映组件UI变化的状态集合;而Props对于使用它的组件来说,是只读的,要想修改Props,只能通过该组件的父组件修改。在组件状态上移的场景中,父组件正是通过子组件的Props, 传递给子组件其所需要的状态。

三. 如何正确修改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修改前的State。另外需要注意的事,同样不能依赖当前的Props计算下个状态,因为Props一般也是从父组件的State中获取,依然无法确定在组件状态更新时的值。

举个例子,对于一个电商类应用,在我们的购物车中,当我们点击一次购买数量按钮,购买的数量就会加1,如果我们连续点击了两次按钮,就会连续调用两次this.setState({quantity: this.state.quantity + 1}),在React合并多次修改为一次的情况下,相当于等价执行了如下代码:

Object.assign(previousState,{quantity: this.state.quantity + 1}, {quantity: this.state.quantity + 1} ) 

于是乎,后面的操作覆盖掉了前面的操作,最终购买的数量只增加了1个。

如果你真的有这样的需求,可以使用另一个接收一个函数作为参数的setState,这个函数有两个参数,第一个是当前最新状态(本次组件状态修改后的状态)的前一个状态preState(本次组件状态修改前的状态),第二个参数是当前最新的属性props。如下所示:

// 正确
this.setState((preState, props) => ({ counter: preState.quantity + 1; })) 

3. State 的更新是一个浅合并(Shallow Merge)的过程。

当调用setState修改组件状态时,只需要传入发生改变的State,而不是组件完整的State,因为组件State的更新是一个浅合并(Shallow Merge)的过程。例如,一个组件的状态为:

this.state = {title : 'React',content : 'React is an wonderful JS library!'
}

当只需要修改状态title时,只需要将修改后的title传给setState

this.setState({title: 'Reactjs'}); 

React会合并新的title到原来的组件状态中,同时保留原有的状态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):

// 方法一:将state先赋值给另外的变量,然后使用concat创建新数组
var books = this.state.books; 
this.setState({ books: books.concat(['React Guide']); }) // 方法二:使用preState、concat创建新数组 this.setState(preState => ({ books: preState.books.concat(['React Guide']); })) // 方法三:ES6 spread syntax this.setState(preState => ({ books: [...preState.books, 'React Guide']; })) 

当从books中截取部分元素作为新状态时,使用数组的slice方法:

// 方法一:将state先赋值给另外的变量,然后使用slice创建新数组
var books = this.state.books; 
this.setState({ books: books.slice(1,3); }) // 方法二:使用preState、slice创建新数组 this.setState(preState => ({ books: preState.books.slice(1,3); })) 

当从books中过滤部分元素后,作为新状态时,使用数组的filter方法:

// 方法一:将state先赋值给另外的变量,然后使用filter创建新数组
var books = this.state.books; 
this.setState({ books: books.filter(item => { return item != 'React'; }); }) // 方法二:使用preState、filter创建新数组 this.setState(preState => ({ books: preState.books.filter(item => { return item != 'React'; }); })) 

注意不要使用push、pop、shift、unshift、splice等方法修改数组类型的状态,因为这些方法都是在原数组的基础上修改,而concat、slice、filter会返回一个新的数组。

3. 状态的类型是普通对象(不包含字符串、数组)

3.1 使用ES6 的Object.assgin方法

// 方法一:将state先赋值给另外的变量,然后使用Object.assign创建新对象
var owner = this.state.owner;
this.setState({ owner: Object.assign({}, owner, {name: 'Jason'}); }) // 方法二:使用preState、Object.assign创建新对象 this.setState(preState => ({ owner: Object.assign({}, preState.owner, {name: 'Jason'}); })) 

3.2 使用对象扩展语法(object spread properties)

// 方法一:将state先赋值给另外的变量,然后使用对象扩展语法创建新对象
var owner = this.state.owner;
this.setState({ owner: {...owner, name: 'Jason'}; }) // 方法二:使用preState、对象扩展语法创建新对象 this.setState(preState => ({ owner: {...preState.owner, name: 'Jason'}; })) 

总结一下,创建新的状态对象的关键是,避免使用会直接修改原对象的方法,而是使用可以返回一个新对象的方法。当然,也可以使用一些Immutable的JS库,如Immutable.js,实现类似的效果。

那么,为什么React推荐组件的状态是不可变对象呢?一方面是因为不可变对象方便管理和调试,了解更多可参考这里;另一方面是出于性能考虑,当对象组件状态都是不可变对象时,我们在组件的shouldComponentUpdate方法中,仅需要比较状态的引用就可以判断状态是否真的改变,从而避免不必要的render调用。当我们使用React 提供的PureComponent时,更是要保证组件状态是不可变对象,否则在组件的shouldComponentUpdate方法中,状态比较就可能出现错误,因为PureComponent执行的是浅比较(比较对象的引用)。



作者:苍山沭河
链接:https://www.jianshu.com/p/c6257cbef1b1
來源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

转载于:https://www.cnblogs.com/chris-oil/p/8215756.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/278663.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

vue-router query,parmas,meta传参

1.query,显示在导航栏?后,相当于get请求传参 this.router.push({path:/login,query:{ redirect:/home}}) this.router.push({name:Login,query:{ redirect:/home}})2.parmas,不会显示,相当于post请求传参,…

Keras 获取中间某一层输出

1.使用函数模型API,新建一个model,将输入和输出定义为原来的model的输入和想要的那一层的输出,然后重新进行predict. 1 #codingutf-82 import seaborn as sbn3 import pylab as plt4 import theano5 from keras.models import Sequential6 fr…

在Windows 7中禁用或修改Aero Peek的“延迟时间”

Are you looking for an easy way to modify the “delay time” for Aero Peek in Windows 7 or perhaps want to disable the feature altogether? Then see how simple it is to do either with the Desktop Peek Tweak. 您是否正在寻找一种简便的方法来修改Windows 7中Aer…

php内核分析(六)-opcode

这里阅读的php版本为PHP-7.1.0 RC3,阅读代码的平台为linux 查看opcode php是先把源码解析成opcode,然后再把opcode传递给zend_vm进行执行的。 // 一个opcode的结构 struct _zend_op {const void *handler; // opcode对应的执行函数,每个opcod…

vue 监听路由变化

一.watch监听$route($router的对象) // 监听,当路由发生变化的时候执行 watch:{$route(to,from){console.log(to.path);} },// 监听,当路由发生变化的时候执行 watch: {$route: {handler: function(val, oldVal){console.log(val);},// 深度观察监听dee…

音频剪切_音频编辑入门指南:剪切,修剪和排列

音频剪切Audacity novices often start with lofty project ideas, but sometimes they lack the basics. Knowing how to cut and trim tracks is basic audio editing and is a fundamental starting point for making more elaborate arrangements. 大胆的新手通常从崇高的项…

Mybatis自定义SQL拦截器

本博客介绍的是继承Mybatis提供的Interface接口,自定义拦截器,然后将项目中的sql拦截一下,打印到控制台。 先自定义一个拦截器 package com.muses.taoshop.common.core.database.config;import org.apache.commons.lang3.StringUtils; import…

搭建spring boot环境并测试一个controller

Idea搭建spring boot环境一、新建项目二、起步依赖三、编写SpringBoot引导类四、编写Controller五、热部署一、新建项目 1.新建project 2.选择SpringInitializr,选择jdk,没有则需要下载并配置(若选择Maven工程则需要自己添加pom.xml所需依赖坐标和Java…

音频噪声抑制_音频编辑入门指南:基本噪声消除

音频噪声抑制Laying down some vocals? Starting your own podcast? Here’s how to remove noise from a messy audio track in Audacity quickly and easily. 放下人声? 开始自己的播客? 这是在Audacity中快速轻松地消除杂乱音轨中噪声的方法。 Th…

Dubbo集群容错

转自dubbo官网文档http://dubbo.apache.org/zh-cn/blog/dubbo-cluster-error-handling.html Design For failure 在分布式系统中,集群某个某些节点出现问题是大概率事件,因此在设计分布式RPC框架的过程中,必须要把失败作为设计的一等公民来对…

Linux基础(day53)

2019独角兽企业重金招聘Python工程师标准>>> 12.21 php-fpm的pool php-fpm的pool目录概要 vim /usr/local/php/etc/php-fpm.conf//在[global]部分增加include etc/php-fpm.d/*.confmkdir /usr/local/php/etc/php-fpm.d/cd /usr/local/php/etc/php-fpm.d/vim www.co…

Mysql+Navicat for Mysql

一、mysql 1.下载安装 Mysql官网下载地址 下载后解压 .zip (或安装.msi) 2.可加入全局变量mysqld (可选) 我的电脑->属性->高级->环境变量->Path(系统变量),添加mysql下的bin目录,如 D:\Pr…

公钥,私钥和数字签名

一、公钥加密 假设一下,我找了两个数字,一个是1,一个是2。我喜欢2这个数字,就保留起来,不告诉你们(私钥),然后我告诉大家,1是我的公钥。 我有一个文件,不能让别人看&…

MySQL中的日志类型(二)-General query log

简介 General query log记录客户端的连接和断开,以及从客户端发来的每一个SQL语句。 日志内容格式 General query log可以记录在文件中,也可以记录在表中,格式如下:在文件中会记录时间、线程ID、命令类型以及执行的语句示例如下&a…

android wi-fi_如何在Android手机上查找3G或Wi-Fi速度

android wi-fiAre you curious about what kind of connection speed you are getting with your Android phone? Today we’ll take a look at how to easily check your Wi-Fi or 3G speeds with Speedtest.net’s Speed Test app. 您是否对Android手机的连接速度感到好奇&a…

vue引入全局less实现全局变量的控制

vue引入全局less1.设置全局样式变量的好处:2.以less为例(sass等同原理)1.vue-cli2搭建的项目(1)2.vue-cli2搭建的项目(2)3.vue-cli3、vue-cli43.vue-cli2和vue-cli3的区别4.vue-cli3和vue-cli4的…

如何在eclipse中对项目进行重新编译

有时由于eclipse异常关闭,当我们重启Eclipse,在启动项目时,会报错,说:ClassNotFound类似的错误,引起这种问题的原因可能是由于,Eclipse异常关闭引起的。 解决:在一个项目中&#xff…

SQL 查询数据库中包含指定字符串的相关表和相关记录

declare str varchar(100)set str我要找的 --要搜索的字符串declare s varchar(8000)declare tb cursor local forselect if exists(select 1 from [b.name] where [a.name] like %str%)print [b.name].[a.name]from syscolumns a join sysobjects b on a.idb.idwhere b.xtype…

如何在Gmail的图片中插入超链接

Adding hyperlinks is an efficient way of getting your reader to the intended web page. Though it’s no secret that you can add hyperlinks to text, Gmail also lets you add hyperlinks to images in the body of the email. Here’s how to make it happen. 添加超链…

内联元素居中

父元素&#xff1a; height:100px; line-height:100px; // 与高相同 text-align:center; 子元素: display:inline; vertical-align: middle; 适用图片、文字 <div><div class"wrapper"><span>我是文字</span></div><div class&qu…