自从咱们学习React的第一天起,咱们就知道不要在React中使用太多状态。咱们也知道应该尽量多使用无状态的函数式组件,少使用有状态的类组件。react
这些建议很容易让咱们造成这样的判断,那就是咱们彻底不该该使用状态,当咱们须要用到状态的时候,应该使用Redux之类的第三方状态管理库。编程
咱们不喜欢使用React原生状态并非没有缘由的:设计模式
严格来说,prop drilling并不属于状态的问题,而是一个设计失误。它一般是由过多的没必要要封装致使的。ide
Redux是最流行的状态管理库,它经过将状态与UI隔离并中心化管理的方式减轻了以上所说的这些痛点。函数式编程
它的整个逻辑十分简单,但却蕴含着强大的扩展性。咱们能够用一个单向数据流图来描述Redux背后的运行机制:函数
除了store自身,全部其余的组件都是纯函数。这是一个函数式编程中的概念,指的是函数的结果只取决于函数的输入,而不取决于任何其余的状态。性能
纯函数更容易测试、理解和调试。经过强制使用函数式编程这一编程风格,Redux减小了维护状态逻辑的负担。学习
然而,Redux也有本身特有的麻烦。一我的们广为诟病的问题就是在搭建项目初期,它须要写太多样板代码。测试
对于小项目而言,这个问题尤其严重。添加一个新的action一般须要咱们定一个新action、添加一个新action creator、修改reducer、更新container等一系列操做。为了完成这个功能,咱们须要在许多个不一样的文件之间跳转,最后把一个5行以内能够作完的事情扩展成20行。优化
甚至说,咱们到底要不要中心化的store也是一个值得讨论的问题,就像咱们要不要用全局变量同样。
和局部状态相比,全局状态更难重用。重构全局状态可能会意外地致使部分现有代码不可用。不合理地使用Redux container也会有性能问题。
在另外一边,近几年React为了让状态管理更好用作出了许多努力,引入了Context API和Hooks等新特性来解决过去的痛点。
React支持两种组件:类组件(支持状态和hook,也就是componentDidMount等函数)和函数式组件(不支持状态,更加简单)。
在过去,若是咱们想使用函数式组件,又想维护某些内部状态,惟一的办法就是使用Redux之类的状态管理库。
在React增长了Context API和hooks以后,咱们有了更多的选择。
由于这两个新API是React官方支持的,我建议在使用第三方的状态管理库以前,先了解一下它们再作决定。
React hooks API提供了一种在函数式组件中管理状态的方法。
这句话第一眼看上去自相矛盾,由于函数式在某种程度上就意味着无状态。可是,咱们先将这一问题搁置在一边,只把函数式组件看成一种设计模式来看待。
和类组件相比,函数式组件有更紧凑的形式,须要更少的样板代码,更可读,对编译器来讲也更容易分析和优化。
然而,不可以在函数式组件中维护状态为咱们带来了极大的不便:即便是引入一个boolean这样小的状态,都须要咱们将整个函数式组件重写为类组件。
React hooks API经过容许咱们在函数式组件中使用状态来解决了这个困境。并且,它也容许咱们将状态逻辑从UI逻辑中剥离出来,重用到其余的UI组件中。
下面这个简短的例子展现了咱们如何在函数式组件中使用React hooks:
import React, { useState } from 'react' const Counter: React.FC = () => { const [counter, setCounter] = useState(0) return ( <div> <p>Counter: {counter}</p> <button onClick={() => setCounter(counter + 1)}> Increment </button> </div> ) }
useState返回当前状态和一个函数(这个函数能够用来更新状态),它的参数是初始状态。这个函数第一次被调用的时候,它将组件的内部状态初始化为给定的初始值。
这个状态是维护在这个组件以内的,不会被同一个组件的多个实例之间共享。
咱们能够在一个函数式组件中调用useState许屡次。React hooks API鼓励咱们将一个复杂的状态分解成多个可重用的简单状态。
咱们能够进一步将状态逻辑封装在单独的函数中(称为custom hook),以便咱们在其余组件中重用这个状态逻辑:
import React, {useState} from 'react' // A custom hook const useCounter = (initial: number) => { const [counter, setCounter] = useState(initial) return { counter, increment () { setCounter(counter + 1) }, reset () { setCounter(initial) } } } const Counter: React.FC = () => { const { counter, increment, reset } = useCounter(0) return ( <div> <p>Counter: {counter}</p> <button onClick={increment}> Increment </button> <button onClick={reset}> Reset </button> </div> ) }
每个使用setState的地方,咱们均可以用React hooks代替,由于React hooks全方面地超越了原来的setState:它容许将一个组件的状态拆分红小的可重用的状态,鼓励咱们多用函数式组件,少用类组件。
React Context API比React hooks更早引入React,可是它是用来解决一个彻底不一样的问题的:状态共享和prop drilling。
这个功能可能会让你联想到Redux的用途,可是React Context API事实上并不鼓励用它来维护一个巨大的中心化store。官方文档中说到:
Context主要是用在数据须要被不一样层次的多个组件访问的时候。尽量少用它,由于它会使组件重用更加困难。
React Context API和React hooks的设计哲学是相同的,那就是尽量避免状态共享。
咱们不得不使用React Context API的典型场景有:
例如,咱们能够经过把UI主题放到Context中的方法来避免手动逐层传递:
import React, { useContext } from 'react' const ThemeContext = React.createContext('light') const UserComponent: React.FC = () => { const theme = useContext(ThemeContext) return ( <div> Current theme: {theme} </div> ) } const App: React.FC = () => { return ( <ThemeContext.Provider value="dark"> <UserComponent /> </ThemeContext.Provider> ) }
经过合理地组合React Context API和React hooks,咱们能够在彻底不用Redux的状况下管理程序状态。
然而,就像编程世界中的其余开放性问题同样,状态管理也没有万金油。具体怎么作取决于业务逻辑的复杂性、程序的规模和其余各类因素。
咱们应该在实际中选择最合适的方法。个人建议是,在项目初期,可使用Context API和React hooks做为开始,随着项目的进行,只在必要的时候才引入Redux。