推荐在FC(function component)中使用,文中基本为FC组件
之前看到过使用Context
去优雅的管理全局数据,这里做一些分享与代码组织方式,文中如果有不对的地方,欢迎大家指正出来!
用Context管理数据,更贴切React原生语法,并且新版ContextAPI性能也得以提升,之前旧版的Context发生改变时,并不能准确的知道具体有哪些子组件需要更新,需要所有组件更新(内部会有小优化去bailout跳过),新版ContextAPI更多像是一种依赖收集,将Context改变挂载在该组件中,就可以明确知道这个组件的依赖改变了,去更新他。
(上图为ReactFiber内Context依赖)
Context使用回顾
使用Context实现全局主题
// index.jsx
const ThemeContext = React.createContext('light');
const Index = () => {
const [theme, setTheme] = React.useState('light');
return (
<ThemeContext.Provider value={theme}>
<App />
</ThemeContext.Provider>
)
}
在某一个具体button
中使用
// Button.jsx
const Button = props => {
// 可直接在该Button中应用主题
const theme = React.useContext(ThemeContext)
return (
<button {...props} />
)
}
这样就避免了组件中充满了无用的透传,使代码变得较为简洁。
通过Context修改全局数据
我们将状态缓存在最顶部,将state
,与setState
都传递下去即可,就达到了缓存在子组件内修改全局状态。
// CountStateContext.js
const CountStateContext = React.createContext();
// index.jsx
const Index = () => {
const [countState, setCountState] = React.useState(0)
return (
<CountStateContext.Provider value={[countState, setCountState]}>
<App />
</CountStateContext.Provider>
)
}
// Sub1.jsx
const Sub1 = () => {
const [countState, setCountState] = React.useContext(CountStateContext)
return (
<>
<h1>Count: {countState}</h1>
<button onClick={() => {setCountState(c => c + 1)}}>Increment CountState</button>
</>
)
}
// Sub2.jsx
const Sub2 = () => {
const [countState] = React.useContext(CountStateContext)
return (
<>
<h1>Count: {countState}</h1>
</>
)
}
// Sub3.jsx
const Sub3 = () => {
const [, setCountState] = React.useContext(CountStateContext)
return (
<>
<button onClick={() => {setCountState(c => c + 1)}}>Increment CountState</button>
</>
)
}
Context都传递下去的问题
将一个顶层状态的state
与setState
都传递下去,但是不一定在所有的子组件中都会用到这两个数据
比如某个子组件中可能只是单纯想进行一个数据的修改,但是由于context
中同时也依赖了state
,所以当顶层组件的state
发生改变时,该组件也会进行一次重新的渲染,那这样其实不符合我们的预期的,所以就将Context存储的数据拆分,那我们的核心就是读写分离!
可能会发生Context
套娃,没关系先把主要问题解决了
// CountContext.js
const CountContext = React.createContext();
const SetCountContext = React.createContext();
// index.jsx
const Index = () => {
const [count, setCount] = React.useState(0)
return (
<CountContext.Provider value={count}>
<SetCountContext.Provider value={setCount}>
<App />
</SetCountContext.Provider>
</CountContext.Provider>
)
}
// Sub1.jsx
const Sub1 = () => {
const count = React.useContext(CountContext)
const setCount = React.useContext(SetCountContext)
return (
<>
<h1>Count: {count}</h1>
<button onClick={() => {setCount(c => c + 1)}}>Increment CountState</button>
</>
)
}
// Sub2.jsx
const Sub2 = () => {
const count = React.useContext(CountContext)
return (
<>
<h1>Count: {count}</h1>
</>
)
}
// Sub3.jsx
const Sub3 = () => {
const setCount = React.useContext(SetCountContext)
return (
<>
<button onClick={() => {setCount(c => c + 1)}}>Increment CountState</button>
</>
)
}
将Context存储的数据抽离出来
目前是将所有的数据都存储在顶层组件中的,我们也可以使用一个自定义Hooks
去存储我们的数据,最终附带吐出一个带有Context
的组件即可
// CountContext.jsx
const CountContext = React.createContext();
const SetCountContext = React.createContext();
function useCount() {
return React.useContext(CountContext)
}
function useSetCount() {
return React.useContext(SetCountContext)
}
function CountStoreProvider({ children }) {
const [count, setCount] = React.useState()
return (
<CountContext.Provider value={count}>
<SetCountContext.Provider value={setCount}>
{children}
</SetCountContext.Provider>
</CountContext.Provider>
)
}
export { useCount, useSetCount, CountStoreProvider }
// index.jsx
import { CountStoreProvider } from './CountContext'
const Index = () => {
return (
<CountStoreProvider>
<App />
</CountStoreProvider>
)
}
// Sub1.jsx
import { useCount, useSetCount } from './CountContext'
const Sub1 = () => {
const count = useCount()
const setCount = useSetCount()
return (
<>
<h1>Count: {count}</h1>
<button onClick={() => {setCount(c => c + 1)}}>Increment CountState</button>
</>
)
}
最终我们创建了一个类似store
的组件,即CountContext
,之后便可以方便的修改我们的数据,将此Context
内部的数据都封装在自定义Hooks内部,也减少了对外部代码的侵入,使用起来比较简洁。
配合Reducer使用
前面一直使用useState
,那么将其存储为useReducer
使用范围会更广,那么将Context
中存储的数据修改为reducer
会更加优雅一点,这一部分就留给大家去改造了。
另外文中一直尝试的修改数据为同步的,如果有异步的数据修改,是将其在React组件内部处理好,再同步的dispatch
出去,还是可以在context
组件内部去做更多异步的处理,需要大家自己去实践了。
优雅!