项目学习 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 (
You clicked {count} times
);
}
render( , 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 (
You clicked {count} times
);
}
function AnotherCounterDisplay() {
const { count, decrement, increment } = useCounter();
return (
当前分数:{count}
);
}
render(
<>
>,
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 (
You clicked {count} times
);
}
function AnotherCounterDisplay(props: { counter: Counter }) {
const { count, decrement, increment } = props.counter;
return (
当前分数:{count}
);
}
function App() {
const counter = useCounter();
return (
<>
>
);
}
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 (
You clicked {count} times
);
}
function AnotherCounterDisplay() {
const { count, decrement, increment } = useContext(CounterContext);
return (
当前分数:{count}
);
}
function CounterInfo() {
const counter = useContext(CounterContext);
return 当前计数:{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 可以是:
-
ReactElementstring(akaReactText)number(akaReactText)- Array of
ReactNodes (akaReactFragment)
他们被用作其他ReactElements的properties来表示子级.事实上他们创建了一个 ReactElements 的树.
稍微实践一下,我们会发现:为自定义 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