译文,原文来自 https://www.robinwieruch.de/l...
译者前注: 翻译仅做为我的学习用途,因本人水平有限,译文中充斥着很多拙劣文法和表述,最好仍是看英文原文.
状态管理是很复杂的.视图层工具库,如React,容许咱们在组件内部管理状态.但它只能扩展到具体某一个组件.React仅仅是一个视图层库.最终你决定(把状态管理)迁移到一个更为成熟的解决方案,如Redux.接下来我想在这篇文章中指出在跳上Redux的列车钱,你应该了解清楚的有关React的内容.html
一般人们会同时学习React和Redux,但这有一些缺点:vue
他们不会遇到在仅使用本地组件状态(this.state)时,扩展状态管理时出现的问题node
他们不会去学习在React中怎么进行本地组件的状态管理react
由于上述缘由,一般建议是先学习React,而后在稍后的时间选择加入Redux.但若是遇到扩展状态管理的问题,就选择加入Redux吧.通常那些扩展问题仅会在较大型的应用程序中存在,一般状况下你不须要Redux这样的状态管理库.学习React之路一书中演示了如何使用普通的React构建应用程序,而不须要用到Redux这样的外部依赖.git
无论怎么样,如今你已经决定拥抱Redux了,所以这里我列出了在使用Redux以前,你应该了解的有关React的内容.github
上面已经提到了最好先学习React,所以你就不能避免使用this.setState()
和this.state
来操做本地状态来为你的组件注入生命.你应该要能游刃有余地使用它们.编程
class Counter extends React.Component { constructor(props) { super(props); this.state = { counter: 0 }; } render() { return ( <div> Counter: {this.state.counter} <button type="button" onClick={() => this.setState({ counter: this.state.counter + 1 })} /> </div> ); } }
一个React组件能够在构造函数中定义初始状态.以后就能够经过this.setState()
方法来更新状态.状态对象(state object)的更新过程是一次浅合并.所以你能够只更新本地状态中特定的某一部分状态,而其他的状态都不会受到影响.一旦状态更新完,组件就会从新渲染.在上面的例子中,应用会展现更新后的值:this.state.counter
.基本上在React的单向数据流中只会存在一条闭环.redux
this.setState()
方法是异步更新本地状态的.所以你不能依赖状态更新的时机.状态最终都会更新的.这在大部分状况下也是没有什么问题的.设计模式
尽管如此,想象一下你的组件须要经过当前状态去计算下一状态.就如同上面的例子那样.数组
this.setState({ counter: this.state.counter + 1 });
用于计算的本地状态(this.state.counter)只是当前时间的一个快照而已.所以当你用this.setState()
更新本地状态时,而本地状态又在异步执行更新完成以前改变了,这时你就操做了一个旧的状态.第一次遇到相似问题的时候或许会有点难以理解.因此用代码来代替千言万语的解释吧:
this.setState({ counter: this.state.counter + 1 }); // this.state: { counter: 0 } this.setState({ counter: this.state.counter + 1 }); // this.state: { counter: 0 } this.setState({ counter: this.state.counter + 1 }); // this.state: { counter: 0 } // 更新事后的state: { counter: 1 } // 而不是: { counter: 3 }
就如你看到的那样,当根据本地状态更新状态时,本地状态做为更新状态.这会致使bug的.这也是为何会有第二种更新React本地状态的方式.
this.setState()
函数能够接受一个函数做为参数而非对象.而这个回调函数的调用会传入在当下this.setState()
异步执行后的本地状态做为参数.这个回调执行的时候就能获取到当前最新的,可信赖的本地状态.
this.setState(previousState => ({ counter: previousState.counter + 1 }));
那么当你须要根据以前的本地状态来更新时,就可使用传入函数给this.setState()
而非对象.
另外,这也适用于依赖props的更新.在异步执行更新以前,从父组件获取到的props也有可能被改变过.因此传入this.setState()
的回调会被注入第二个参数props.
this.setState((prevState, props) => ...);
这样你就能保证更新状态时所依赖的state和props是正确的.
this.setState((prevState, props) => ({ counter: prevState.counter + props.addition }));
使用回调函数时的另一个好处是能单独对状态更新进行测试.简单地把this.setState(fn)
中的回调函数提取出来并导出(export)便可.这个回调函数应该是一个纯函数,你能够根据输入进行简单的输出测试.
State是组件内部维护状态.能够做为其余组件的Props向下传递.那些接受Props的组件能够在内部使用Props,或者再进一步向下传递给它们的子组件.另外子组件接受的Props仲一样能够是来自父组件的回调函数.那些函数能够用于改变父组件State.基本上Props随着组件树往下传递,而State则由组件本身维护,此外经过往上层组件冒泡的函数能够改变组件中的State,而更新事后的State又以Props的形式往下传递.
组件能够管理不少State,这些State能够做为Props往下传递给子组件而且Props中能够传递函数给予子组件修改父组件的State.
可是,子组件并不知道Props中的函数的来源或功能.这些函数能够更新父组件的State,也多是执行其余操做.一样的道理,子组件也不知道它所接收的Props是来自父组件的Props,State或其余派生属性,子组件只是单纯的使用它们而已.
掌握并理解State和Props很是重要,组件树中使用的全部属性均可以被分为State和Props(以及根据State和Props计算得出的派生属性).全部须要交互的部分都应做为State保存,而其余的一切均可以做为Props在组件树中传递.
在使用复杂的状态管理工具库以前,你应该已经试过在组件树中往下传递Props.当你传递Props给一些根本不使用它们的组件,而又须要这些组件把Props继续向下传递给最后一个使用它们的子组件时,你应该已经感受到"须要一种更好的方式来作这件事".
你是否已经提取出你的本地状态层?这是在React中扩展本地状态管理最重要的策略.状态层是能够上下提取的.
你能够向下提取的你的本地状态,使其余组件无法访问.假设你有一个组件A是组件B和组件C的父组件,B和C是A的子组件,而且B,C为兄弟组件.组件A是惟一维护本地状态(State)的组件,可是它会将State做为Props传递给子组件.另外也传递了必要的函数让B和C可以改变A中的State.
+----------------+ | | | A | | | | Stateful | | | +--------+-------+ | +---------+-----------+ | | | | +--------+-------+ +--------+-------+ | | | | | | | | | B | | C | | | | | | | | | +----------------+ +----------------+
如今组件A的State中有一半做为Props传递给C并为C所用,但B并不须要那些Props.另外,C使用其接收的Props中的函数来改变A中仅传递给了C的那部分State.如图所示,组件A在帮助组件C维护着State.在大多数状况下,只须要一个组件管理其子组件全部的State便可.可是想象一下,若是组件A和组件C中间还有其余组件,而组件A依然是在维护着组件C所需的状态,那由组件A往下传递的全部Props都须要遍历组件树才能最终到达组件C.
+----------------+ | | | A | | | | | | Stateful | +--------+-------+ | +---------+-----------+ | | | | +--------+-------+ +--------+-------+ | | | | | | | + | | B | | |Props | | | | v | | | | | +----------------+ +--------+-------+ | +--------+-------+ | | | + | | |Props | | v | | | +--------+-------+ | +--------+-------+ | | | | | C | | | | | +----------------+
这是展现提取React State的完美用例.当State仅仅用于组件C而组件A只是充当了维护的角色.这个时候对应的State片断就能够在在C中单独管理,是能够被独立出来的.将State状态管理提取出来到组件C后,就不会出现传递Props须要遍历整个组件树的状况了.
+----------------+ | | | A | | | | | | Stateful | +--------+-------+ | +---------+-----------+ | | | | +--------+-------+ +--------+-------+ | | | | | | | | | B | | | | | | | | | | | +----------------+ +--------+-------+ | +--------+-------+ | | | | | | | | | | +--------+-------+ | +--------+-------+ | | | | | C | | | | Stateful | +----------------+
此外,组件A中的State也应有所改变,它只管理本身以及其最接近的子组件的必要State.
提取State不只能够往下,也能够反过来往上提取:提高状态.假设你有父组件A,组件B和C都为其子组件.A和B以及A和C之间又多少个组件并不重要,可是此次组件C已经管理了它所需的全部State.
+----------------+ | | | A | | | | | | Stateful | +--------+-------+ | +---------+-----------+ | | | | +--------+-------+ +--------+-------+ | | | | | | | | | B | | | | | | | | | | | +----------------+ +--------+-------+ | +--------+-------+ | | | | | C | | | | Stateful | +----------------+
若是组件B须要组件C中所管理的State呢?这个时候组件C中的State不能共享给组件B,由于State只能做为Props向下传递.这就是为何你须要提高State.你能够把组件C中的State网上提取,直到B和C的共同父组件(A),若是组件B须要用到组件C中管理的全部状态,则组件C甚至应该变成无状态组件.而全部的State能够在A中管理,但在B和C之间共享.
+----------------+ | | | A | | | | | | Stateful | +--------+-------+ | +---------+-----------+ | | | | +--------+-------+ +--------+-------+ | | | | | | | + | | B | | |Props | | | | v | | | | | +----------------+ +--------+-------+ | +--------+-------+ | | | | | C | | | | | +----------------+
提取State让你可以仅仅经过React就能扩展你的状态管理.当更多的组件须要用到特定的State时,能够往上提取State,直到须要访问该State的组件的公共组件.此外,本地状态管理依然保持着可维护性,由于一个组件根据自身需求管理尽量多的状态,换言之若是组件或其子组件不须要该State的话,则能够往下提取State放置在须要的地方.
你能够在官方文档中阅读更多关于关于提取State的信息.
高阶组件是React中一种高级设计模式.你可使用它来抽象功能,并将其做为其余多个组件的可选功能重用.高阶组件接受一个组件和其余可选配置做为参数并返回一个加强版本的组件.它创建在Javascript的高阶函数(返回函数的函数)的原则之上.
若是你并不十分了解高阶组件的概念,我推荐你阅读一下高阶组件的简单介绍.里面经过React的条件渲染用例来说解高阶组件的概念.
高阶组件概念在后面会显得尤其重要,由于在使用像Redux这样的库的时候,你将会遇到不少高阶组件.当须要使用Redux这一类库将状态管理层和React的视图层"链接"起来时.你一般会使用一个高阶组件来处理这层关系(如react-redux中的connect高阶组件).
这也一样适用于其余状态管理库,如MobX.在这些库中使用了高阶组件来处理状态管理层和视图层的链接.
React的Context上下文不多被使用,我不会建议去使用它,由于Context API并不稳定,并且使用它还UI增长应用程序的复杂性.不过尽管如此,仍是颇有必要理解它的功能的.
因此为何你应该要了解Context呢?Content用于在组件树上隐式地传递属性.你能够在父组件的某个地方声明属性,并在组件树下的某个子组件中选择再次获取该属性.然而若是经过Props传递的话,全部不须要使用那些数据的组件都须要显式地往下传递.这些组件位于父组件的上下文和最终消费该Props的子组件的上下文之间.因此Context是一个无形的容器.你能够在组件树中找到它.因此,为何你应该要了解Context呢?
一般在使用复杂的状态管理工具库时,例如Redux和MobX,你须要将状态管理层粘合到React的视图层上.这也是为何你须要了解React高阶组件的缘由.这其中的粘合层容许你访问State并进行修改,而State自己一般是在某种状态容器中进行管理的.
可是如何使这个状态容器可以被全部粘合上React组件所访问呢?这是由React Context来完成的.在最顶层的组件,通常是React应用的根组件,你应在React Context中声明状态容器,以便在组件树下的每一个组件都能进行隐式访问.整个过程都是经过React的提供者模式(Provider Pattern)完成的.
固然这也并不意味着在使用Redux一类的库时你须要本身处理React Context上下文.这类工具库已经为你提供了解决方案,使全部组件均可以访问状态容器.当你的状态能够在不一样的组件中访问而没必要担忧状态容器来自哪里的时后,这个底层实现的机制是什么,为何这样作的有效的,这都是颇有必要去了解的事实.
React中有两种声明组件的方式: ES6类组件和函数(不带状态)组件.一个不带状态的函数组件仅仅是一个接收Props并返回JSX的函数.其中不保持任何的State也不会触发任何React生命周期函数.顾名思义就是无状态的.
function Counter({ counter }) { return ( <div> {counter} </div> ); }
另外一方面,即React类组件是能够保持State和能出发声明周期函数的.这些组件能访问this.state
和调用this.setState()
方法.这就说明了ES类组件是能带状态的组件.而若是他们不须要保持本地State的话,也能够是无状态组件.一般无状态的类组件也会须要使用声明周期函数.
class FocusedInputField extends React.Component { constructor(props) { super(props); } componentDidMount() { this.input.focus(); } render() { return ( <input type="text" value={this.props.value} ref={node => this.input = node} onChange={event => this.props.onChange(event.target.value)} /> ); } }
结论是只有ES6类组件是能够带状态的,可是他们也能够是无状态的.而函数组件则是无状态的.
此外,还可使用高阶组件来添加状态到React组件上.你能够编写本身的高阶组件来管理状态,或者使用像recompose工具库中的withState
高阶组件.
import { withState } from `recompose`; const enhance = withState('counter', 'setCounter', 0); const Counter = enhance(({ counter, setCounter }) => <div> Count: {counter} <button onClick={() => setCounter(n => n + 1)}>Increment</button> <button onClick={() => setCounter(n => n - 1)}>Decrement</button> </div> );
当使用高阶组件时,你能够选择传递任意局部状态到React组件中去.
由于Dan Abramov的博文,容器和演示模式变得流行了起来.若是你对此并不十分了解,如今正是深刻学习的时机.基本上它会将组件分为两类:容器组件和展现组件.容器组件负责描述组件是如何工做的,展现组件负责组件内容的展现.容器组件通常是一个类组件,由于容器组件是须要管理本地状态的.而展现组件是一个无状态函数组件,由于通常只用于展现Props和调用从父组件传递过来的函数.
在深刻Redux以前,理解这种模式背后的原理是颇有意义的.当你使用状态管理的工具库时,你会把组件和State链接起来.那些组件并不在乎应该怎么去展现内容,而更可能是描述如何起效的.所以那些组件就是容器组件.再具体一点,你应该会常常听到链接组件(connected component)
当一个组件和状态管理层链接起来以后.
纵观全部状态管理库,Redux是最流行的一个,可是MobX也是一个颇有价值的替代品.这两个库都遵循不一样的哲学和编程范式.
在你决定使用它们以前,请确保你清楚了解本文中解释的有关React的内容.你应该对能坦然面对在仅使用React的状况下进行本地状态管理,还能了解各类React的概念并扩展你的状态管理.此外,确保在你的应用在将来会变得更加庞大时,才须要去扩展状态管理的解决方案.也许提取State或使用React Context应用提供者模式(provider pattern)就能够解决你的问题了.
所以,若是决定迈上Redux和MobX的道路,能够阅读下面的文章以作出更好的决定:Redux or MobX: An attempt to dissolve the Confusion.文章中有效的对比了两个库的差别,并提供了一些学习和应用他们的建议.或者看下Tips to learn React + Redux来了解Redux吧.
但愿这篇文章为你理清了再应用像Redux一类的库以前,你应该学习和了解的内容.目前,我正在写一个关于Redux和本地状态管理的书,内容包括Redux和MobX.若是不想错过的话,你能够点这进行订阅.
译者后注: 但愿我拙劣的翻译没有为你理解本文增长难度,再说一次最好仍是看英文原文,若有改进建议请大方联系,我必虚心受教.