说说React组件的State

简介:

React的核心思想是组件化的思想,应用由组件搭建而成, 而组件中最重要的概念是State(状态)。

正确定义State

React把组件看成一个状态机。通过与用户的交互,实现不同状态,然后渲染UI,让用户界面和数据保持一致。组件的任何UI改变,都可以从State的变化中反映出来;State中的所有状态都用于反映UI的变化,不应有多余状态。

那么什么样的变量应该做为组件的State呢:

  1. 可以通过props从父组件中获取的变量不应该做为组件State。

  2. 这个变量如果在组件的整个生命周期中都保持不变就不应该作为组件State。

  3. 通过其他状态(State)或者属性(Props)计算得到的变量不应该作为组件State。

  4. 没有在组件的render方法中使用的变量不用于UI的渲染,那么这个变量不应该作为组件的State 。这种情况下,这个变量更适合定义为组件的一个普通属性。

React中的immutability

React官方建议把State当做是不可变对象,State中包含的所有状态都应该是不可变对象,当State中的某个状态发生变化,我们应该重新创建这个状态对象,而不是直接修改原来的状态。State根据状态类型可以分为三种。

  1. 数字,字符串,布尔值,null,undefined这五种不可变类型。

因为其本身就是不可变的,如果要修改状态的话,直接赋新值就可以,例如:


 this.setState({   num: 1,   string: 'hello',   ready: true });

2、数组类型

js中数组类型为可变类型。假如有一个state是数组类型,例如students。修改students的状态应该保证不会修改原来的状态,例如新增一个数组元素,应使用数组的concat方法或ES6的数组扩展语法


let students = this.state.students;this.setState({  students: students.concat(['xiaoming'])});//或者this.setState(preState => ({  students: [ ...preState.books, 'xiaogang']});

从数组中截取部分作为新状态时,应使用slice方法;当从数组中过滤部分元素后,作为新状态时,使用filter方法。不应该使用push、pop、shift、unshift、splice等方法修改数组类型的状态,因为这些方法都是在原数组的基础上修改的。应当使用不会修改原数组而返回一个新数组的方法,例如concat、slice、filter等。

  1. 普通对象

对象也是可变类型,修改对象类型的状态时,应该保证不会修改原来的状态。可以使用ES6的Object.assign方法或者对象扩展语法。


//Object.assign方法this.setState(preState => ({  school: Object.assign({}, preState.school, {classNum: 10})}));//对象扩展语法let school = this.state.school;this.setState({  school: { ...school, { classNum: 10 } }})

不同方式创建的组件中的State

  1. 无状态组件(Stateless Functional Component)

这种组件自身没有状态,不需要管理state状态,所有数据都是从props传入。


const Teacher = ({  name,  age}) => {  return (    <div>Teacher {name} is {age} years old.</div>  )}

相同的输入(props)必然有相同的输出,因此这种组件可以写成无副作用的纯函数,且适合函数式编程(函数的compose,curring等组合方式)

  1. 纯组件(PureComponent)

我们知道,当组件的props或者state发生变化的时候React会对组件当前的Props和State分别与nextProps和nextState进行比较,当发现变化时,就会对当前组件以及子组件进行重新渲染,否则就不渲染。有时候我们会使用shouldUpdateComponent来避免不必要的渲染。当然有时候这种简单的判断,显得有些多余和样板化,于是react就提供了PureComponent来自动帮我们完成这件事,简化了我们的代码,提高了性能。例如:


class CounterButton extends React.pureComponent {  constructor(props) {    super(props)    this.state = { count: 1 };  }  render() {    return (      <button         color={this.props.color}        onClick={() => this.setState(state = > ({count: state.count + 1}))}      >       Count: {this.state.coount}      </button>    )  }}

在上例中,虽然没有添加shouldUpdateComponent代码,但是react自动完成了props和state的比较,当props和state没有发生变化时不会对组件重新渲染。但是PureComponent的自动为我们添加的shouldComponentUpate函数,只是对props和state进行浅比较(shadowcomparison),当props或者state本身是嵌套对象或数组等时,浅比较并不能得到预期的结果,这会导致实际的props和state发生了变化,但组件却没有更新的问题。

浅比较:比较 Object.keys(state | props) 的长度是否一致,每一个 key 是否两者都有,并且是否是一个引用,也就是只比较了第一层的值,确实很浅,所以深层的嵌套数据是对比不出来的。

例如

class Ul extends PureComponent {  constructor(props) {    super(props)    this.state = {       items: [1, 2, 3]     };  }  handleClick = () => {    let { items } = this.state;    items.push(4);    this.setState({ items });  }  render() {    return (    <div>      <ul>        {this.state.items.map(i => <li key={i}>{i}</li>)}      </ul>      <button onClick={this.handleClick}>add</button>    </div>)  }}
会发现,无论怎么点 add 按钮, li 都不会变多,因为 pop方法是在原数组上进行的修改,items的preState与nextState 用的是一个引用, shallowEqual 的结果为 true 。改正:

handleClick = () => {  let { items } = this.state;  this.setState({ items: items.concat([4]) });}

这样每次改变都会产生一个新的数组,items的preState与nextState 用的是不同引用, shallowEqual 的结果为 false,也就可以 render 了。在PureComponent中要避免可变对象作为props和state,你可以考虑使用Immutable.js来创建不可变对象,Immutable Data就是一旦创建,就不能再被更改的数据。对 Immutable 对象的任何修改或添加删除操作都会返回一个新的 Immutable 对象。从而避免出现props和state发生改变而组件没有重新渲染的问题。

  1. Component

与PureComponent不同的是,Component需要开发者显示定义shouldUpdateComponent且定制性更强。对于一些无论怎么修改都不应该让组件重新渲染的props就不必在shouldUpdateComponent中进行比较。同PureComponent一样Component中的state也应该是不可变对象。

使用Object.assign或者concat等方法避免修改原来的对象或数组是通过将属性/元素从一个对象/数组复制到另一个来工作,这种拷贝方式只是浅拷贝。Object.assign()方法实行的就是浅拷贝,而不是深拷贝。也就是说源对象某个属性的值是对象,那么目标对象拷贝得到的是这个对象的引用。例如有school状态:


this.state = {  school: {    classOne: {      ...    },    classTwo: {      ...    }  }}

通过Object.assign修改状态

let classOne = {  teacher: [...],  students: [...],};this.setState(preState => ({  school: Object.assign({}, preState.school, {classOne: classOne})}));

上面代码中Object.assign拷贝的只是classOne对象的引用,任何对classOne的改变都会反映到React的State中。深拷贝并不是简单的复制引用,而是在堆中重新分配内存,并且把源对象实例的所有属性都新建复制,以保证复制的对象的引用不指向任何原有对象上或其属性内的任何对象,复制后的对象与原来的对象是完全隔离的(关于深拷贝可参考这篇文章)。深拷贝通常需要把整个对象递归的复制一份,十分影响性能。对于大型对象/数组来说,操作比较慢。当应用复杂后,props和state也变得复杂和庞大,通过浅拷贝和深拷贝就会影响性能和造成内存的浪费。且对象和数组默认是可变的,没有什么可以确保state是不可变对象,你必须时刻记住要使用这些方法。

使用immutable.js可以很好的解决这些问题。Immutable.js 的基本原则是对于不变的对象返回相同的引用,而对于变化的对象,返回新的引用。同时为了避免 deepCopy 把所有节点都复制一遍带来的性能损耗,Immutable 使用了 Structural Sharing(结构共享),即如果对象树中一个节点发生变化,只修改这个节点和受它影响的父节点,其它节点则进行共享。(关于immutab.js可以看这篇文章或者immutable.js)

总结

正确的定义state不仅便于状态的管理与调试,而且在复杂应用中保持state的简洁,在组件更新时能减少比较次数,提高性能。保证state的不可变性不仅保证数据更容易追踪、推导,而且能避免组件更新时shouldComponent出现状态比较错误。


原文发布时间为:2018年07月03日
原文作者: 掘金
本文来源:  掘金  如需转载请联系原作者


相关文章
|
7天前
|
前端开发 JavaScript 测试技术
React 分页组件 Pagination
本文介绍了如何在 React 中从零构建分页组件,涵盖基础概念、常见问题及解决方案。通过示例代码详细讲解了分页按钮的创建、分页按钮过多、初始加载慢、状态管理混乱等常见问题的解决方法,以及如何避免边界条件、性能优化和用户反馈等方面的易错点。旨在帮助开发者更好地理解和掌握 React 分页组件的开发技巧,提升应用的性能和用户体验。
32 0
|
1月前
|
前端开发 开发者
React 函数组件与类组件对比
【10月更文挑战第4天】本文详细比较了React中的函数组件与类组件。函数组件是一种简单的组件形式,以纯函数的形式返回JSX,易于理解与维护,适用于简单的UI逻辑。类组件则是基于ES6类实现的,需要重写`render`方法并能利用更多生命周期方法进行状态管理。文章通过示例代码展示了两者在状态管理与生命周期管理上的差异,并讨论了常见的问题如状态更新异步性与生命周期管理的复杂性,最后给出了相应的解决方法。通过学习,开发者可以根据具体需求选择合适的组件类型。
55 8
|
1月前
|
人工智能 自然语言处理 前端开发
SpringBoot + 通义千问 + 自定义React组件:支持EventStream数据解析的技术实践
【10月更文挑战第7天】在现代Web开发中,集成多种技术栈以实现复杂的功能需求已成为常态。本文将详细介绍如何使用SpringBoot作为后端框架,结合阿里巴巴的通义千问(一个强大的自然语言处理服务),并通过自定义React组件来支持服务器发送事件(SSE, Server-Sent Events)的EventStream数据解析。这一组合不仅能够实现高效的实时通信,还能利用AI技术提升用户体验。
163 2
|
12天前
|
移动开发 前端开发 API
React 拖拽组件 Drag & Drop
本文介绍了在 React 中实现拖拽功能的方法,包括使用原生 HTML5 Drag and Drop API 和第三方库 `react-dnd`。通过代码示例详细讲解了基本的拖拽实现、常见问题及易错点,帮助开发者更好地理解和应用拖拽功能。
41 9
|
6天前
|
前端开发 UED 开发者
React 分页组件 Pagination
本文介绍了如何在 React 中实现分页组件,从基础概念到常见问题及解决方案。分页组件用于将大量数据分成多个页面,提升用户体验。文章详细讲解了分页组件的基本结构、快速入门步骤、以及如何处理页面跳转不平滑、页码过多导致布局混乱、边界条件处理和数据加载延迟等问题。通过本文,读者可以全面了解并掌握 React 分页组件的开发技巧。
12 2
|
10天前
|
设计模式 前端开发 编译器
与普通组件相比,React 泛型组件有哪些优势?
与普通组件相比,React 泛型组件有哪些优势?
28 6
|
19天前
|
前端开发 JavaScript 安全
学习如何为 React 组件编写测试:
学习如何为 React 组件编写测试:
34 2
|
26天前
|
前端开发 JavaScript 测试技术
React 高阶组件 (HOC) 应用
【10月更文挑战第16天】高阶组件(HOC)是 React 中一种复用组件逻辑的方式,通过接受一个组件并返回新组件来实现。本文介绍了 HOC 的基础概念、核心功能和常见问题,包括静态方法丢失、ref 丢失、多个 HOC 组合和 props 冲突的解决方案,并提供了具体的 React 代码示例。通过本文,读者可以更好地理解和应用 HOC,提高代码的复用性和可维护性。
56 8
|
25天前
|
缓存 前端开发 JavaScript
前端serverless探索之组件单独部署时,利用rxjs实现业务状态与vue-react-angular等框架的响应式状态映射
本文深入探讨了如何将RxJS与Vue、React、Angular三大前端框架进行集成,通过抽象出辅助方法`useRx`和`pushPipe`,实现跨框架的状态管理。具体介绍了各框架的响应式机制,展示了如何将RxJS的Observable对象转化为框架的响应式数据,并通过示例代码演示了使用方法。此外,还讨论了全局状态源与WebComponent的部署优化,以及一些实践中的改进点。这些方法不仅简化了异步编程,还提升了代码的可读性和可维护性。
|
13天前
|
前端开发 UED
React 模态框 Modal 组件详解
【10月更文挑战第27天】本文介绍了如何在 React 中实现一个功能完善的模态框组件。从基础概念入手,逐步讲解了简单的模态框实现、CSS 样式、传递子组件、键盘事件处理等高级功能。同时,还探讨了常见问题及易错点,如背景点击关闭、键盘事件冲突和动画效果。通过本文,读者可以全面了解 React 模态框组件的实现细节。
27 0