项目学习 3 —— 使用Context和hook做状态管理
使用Context和hook做状态管理
学习过 React Hook,就会知道自定义 Hook 可以封装状态管理逻辑,并达到复用、共享的效果。
现在我们有一个计时器??
import React, { useState } from 'react'; import { render } from 'react-dom'; function CounterDisplay() { const [count, setCount] = useState(0); const decrement = () => setCount(count - 1); const increment = () => setCount(count + 1); return (); } render(You clicked {count} times
, document.getElementById('root'));
如果我们要复用计数器状态管理这部分代码,我们可以使用自定义 hook:
import React, { useState } from 'react'; import { render } from 'react-dom'; function useCounter() { const [count, setCount] = useState(0); const decrement = () => setCount(count - 1); const increment = () => setCount(count + 1); return { count, decrement, increment }; } function CounterDisplay() { const { count, decrement, increment } = useCounter(); return (); } function AnotherCounterDisplay() { const { count, decrement, increment } = useCounter(); return (You clicked {count} times
); } render( <>当前分数:{count}
>, document.getElementById('root'), );
通过努力,CounterDisplay
和AnotherCounterDisplay
两个组件共享了计数状态管理逻辑。但如果要求这两个组件共享状态怎么办?这时候,我们可能会想到状态提升,如下所示:
import React, { useState } from 'react'; import { render } from 'react-dom'; interface Counter { count: number; decrement: () => void; increment: () => void; } function useCounter(): Counter { const [count, setCount] = useState(0); const decrement = () => setCount(count - 1); const increment = () => setCount(count + 1); return { count, decrement, increment }; } function CounterDisplay(props: { counter: Counter }) { const { count, decrement, increment } = props.counter; return (); } function AnotherCounterDisplay(props: { counter: Counter }) { const { count, decrement, increment } = props.counter; return (You clicked {count} times
); } function App() { const counter = useCounter(); return ( <>当前分数:{count}
> ); } render( , document.getElementById('root'));
如果需要共享状态的组件与共同父组件层级比较深,那么我们可以使用 React Context 简化状态提升需要逐级传输组件属性:
import React, { useState, useContext } from 'react'; import { render } from 'react-dom'; interface Counter { count: number; decrement: () => void; increment: () => void; } function useCounter(): Counter { const [count, setCount] = useState(0); const decrement = () => setCount(count - 1); const increment = () => setCount(count + 1); return { count, decrement, increment }; } const CounterContext = React.createContext(defaultValue); function CounterDisplay() { const { count, decrement, increment } = useContext(CounterContext); return ( ); } function AnotherCounterDisplay() { const { count, decrement, increment } = useContext(CounterContext); return (You clicked {count} times
); } function CounterInfo() { const counter = useContext(CounterContext); return当前分数:{count}
当前计数:{counter.count}; } function Header() { return (); } function App() { const counter = useCounter(); return (计数器
); } render(, document.getElementById('root'));
??注意:
React.createContext(defaultValue)
指的是 传入的参数 defaultValue 是 Counter 类型
现在要求AnotherCounterDisplay
的状态单独管理,依然可以用 Context:
function App() { const counter = useCounter(); const anotherCounter = useCounter(); return (); }
我们也可以将再创建一个组件,专门用来提供计数状态管理的上下文:
function CounterContextProvider({ children }: { children: React.ReactNode }) { const counter = useCounter(); return ({children} ); }
??注意:
{ children }: { children: React.ReactNode }
{children}:
变量是个对象,对象里面是children
{ children: React.ReactNode }
变量里面的children 是个 React.ReactNode 类型
一个 ReactNode
可以是:
-
ReactElement
string
(akaReactText
)number
(akaReactText
)- Array of
ReactNode
s (akaReactFragment
)
他们被用作其他ReactElement
s的properties来表示子级.事实上他们创建了一个 ReactElement
s 的树.
稍微实践一下,我们会发现:为自定义 hook 创建的上下文这种模式很有用。我们来整理一下这种模式的计数器例子:
interface Counter { count: number; decrement: () => void; increment: () => void; } // 首先定义一个React Hook function useCounter() { const [count, setCount] = useState(0); const decrement = () => setCount(count - 1); const increment = () => setCount(count + 1); return { count, decrement, increment }; } // 然后定义一个上下文: const CounterContext = React.createContext(null); // 之后我们创建一个提供上下文的Provider组件: function CounterContextProvider({ children }: { children: React.ReactNode }) { const counter = useCounter(); return ( {children} ); } // 之后,我们就可以尽情地使用了: function App() {; } function CounterDisplay() { const counter = useContext(CounterContext); return {counter.count}; }
这种模式的核心点就是需要自定义 hook。然后都会有第二步和第三步,那么我们可以继续提炼一下(第二步和第三步):
//func: () => T function createContainer(func:Function):T { const ContainerContext = React.createContext null>(null); const Provider = ({ children }: { children: React.ReactNode }) => { const result = func(); return ( value={result}> {children} ); }; const useContainer = () => { return useContext(ContainerContext); }; return { Provider, useContainer }; }
我们使用createContainer
来简化自定义 hook 上下文这种模式:
// 首先定义一个React Hook function useCounter() { const [count, setCount] = useState(0); const decrement = () => setCount(count - 1); const increment = () => setCount(count + 1); return { count, decrement, increment }; } // 然后定义计数容器 const CounterContainer = createContainer(useCounter); // 之后,我们就可以尽情地使用了: function App() {; } function CounterDisplay() { const counter = CounterContainer.useContainer(); return {counter.count}; }
这里,我们引入了一个container
名词,用来表示包装自定义 hook 到上下文,我们姑且称之为“hook 容器”,或者简称为“容器”。
刚刚的createContainer
已经由unstated-next实现:
安装unstated-next
yarn add unstated-next
import { createContainer } from 'unstated-next'; // 首先定义一个React Hook function useCounter() { const [count, setCount] = useState(0); const decrement = () => setCount(count - 1); const increment = () => setCount(count + 1); return { count, decrement, increment }; } // 然后定义计数容器 const CounterContainer = createContainer(useCounter); // 之后,我们就可以尽情地使用了: function App() {; } function CounterDisplay() { const counter = CounterContainer.useContainer(); return {counter.count}; }
页面组件的状态管理与“hook 容器”模式
将状态管理逻辑与 UI 逻辑分离开 ---- React Hooks。
对于一个页面组件来说,我们使用组件来处理 UI 渲染,使用 React Hooks 来处理状态。大概率下页面各个部分需要共享状态。这样分析,你会发现“hook 容器”模式非常适合页面组件的开发:将页面级别的状态管理放在页面自定义 hook 中,页面的各个子组件都可以通过上下文快速获取到需要的共享状态。
function useXxxxPage() { .... } const XxxxPageContainer = createContainer(useXxxxPage); function XxxxPageHeader() { const xxxxPageState = XxxxPageContainer.useContainer(); //.... } function XxxxPageContent() { const xxxxPageState = XxxxPageContainer.useContainer(); //... } function XxxxPageFooter() { const xxxxPageState = XxxxPageContainer.useContainer(); //... } function XxxxPage() { return}
强调一点:在页面级别需要共享的数据才需要放到useXxxxPage
中。局部状态依然首推在局部组件级别解决。
页面组件也是组件,在 React 中没有任何特殊的设定,只是页面组件往往会面临状态的跨级共享,而且我们在开发应用时,一般会从页面组件开始,所以,我们可以选择一种状态管理模式作为状态管理的参考实现,“hook 容器”就是一种好的模式。但是页面组件的状态管理同样需要遵循组件状态管理的最佳实践,当发现“hook 容器”不适合时,应该考虑其他的最佳实践。
在做应用开发时,遵循以下几个要点:
- 用组件做 UI 渲染
- 用组件做 UI 渲染逻辑复用
- 用 React Hooks 做组件状态管理
- 用自定义 hook 做状态管理逻辑复用
- 用自定义 hook 做状态管理逻辑与 UI 渲染逻辑分离
- 遇到跨级共享状态时,用 React Context
- 如果用 React Context + custom hooks 做跨级共享状态,可以考虑用 unstated-next
unstated-next 使用要点
- 要点#1: 保持 Containers 很小
- 要点#2:组合 Containers
- 要点#3:优化组件
转载自:https://sinoui.github.io/sinoui-guide/docs/context-and-hook