深入理解React 组件状态(State)
React 的核心思想是组件化的思想,应用由组件搭建而成,而组件中最重要的概念是State(状态),State是一个组件的UI数据模型,是组件渲染时的数据依据。 如何定义State定义一个合适的State,是正确创建组件的第一步。State必须能代表一个组件UI呈现的完整状态集,即组件的任何UI改变,都可以从State的变化中反映出来;同时,State还必须是代表一个组件UI呈现的最小状态集,即State中的所有状态都是用于反映组件UI的变化,没有任何多余的状态,也不需要通过其他状态计算而来的中间状态。 组件中用到的一个变量是不是应该作为组件State,可以通过下面的4条依据进行判断:
请务必牢记,并不是组件中用到的所有变量都是组件的状态!当存在多个组件共同依赖一个状态时,一般的做法是状态上移,将这个状态放到这几个组件的公共父组件中。 State 与 Props 区别除了State,组件的Props也是和组件的UI有关的。他们之间的主要区别是:State是可变的,是组件内部维护的一组用于反映组件UI变化的状态集合;而Props对于使用它的组件来说,是只读的,要想修改Props,只能通过该组件的父组件修改。在组件状态上移的场景中,父组件正是通过子组件的Props,传递给子组件其所需要的状态。 如何正确修改State1.不能直接修改State。直接修改state,组件并不会重新重发render。例如: // 错误 this.state.title = 'React'; 正确的修改方式是使用 // 正确 this.setState({title: 'React'}); 2. State 的更新是异步的。调用 举个例子,对于一个电商类应用,在我们的购物车中,当我们点击一次购买数量按钮,购买的数量就会加1,如果我们连续点击了两次按钮,就会连续调用两次 Object.assign( previousState,{quantity: this.state.quantity + 1},{quantity: this.state.quantity + 1} ) 于是乎,后面的操作覆盖掉了前面的操作,最终购买的数量只增加了1个。 如果你真的有这样的需求,可以使用另一个接收一个函数作为参数的 // 正确 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!' } 当只需要修改状态 this.setState({title: 'Reactjs'}); React会合并新的 { title : 'Reactjs',content : 'React is an wonderful JS library!' } State与ImmutableReact官方建议把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推荐组件的状态是不可变对象呢?一方面是因为不可变对象方便管理和调试,了解更多可参考这里;另一方面是出于性能考虑,当对象组件状态都是不可变对象时,我们在组件的 (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |