createContext
createContext api 可以创建一个 React 的 上下文对象,如果使用了这个上下文对象中Provider组件,就可以拿到上下文中提供的数据或者其它信息。
使用方式:
代码语言:javascript复制const defaultValue = {}
const MyContext = React.createContext(defaultValue)
如果要使用创建的上下文,需要通过 Context 对象上的 Provider 最外层包装组件,使用方式如下:
代码语言:javascript复制<MyContext.Provider value={{ a: 123, b: 222, fn: () => console.log('fn1')))) }}>
<A>
<MyContext1.Provider value={{ fn: () => console.log('fn2'))) }}>
<B>
<C></C>
</B>
</MyContext1.Provider>
</A>
</MyContext.Provider>
需要通过上面的方式传入value值,指定 Context 要暴露的信息。
子组件在匹配过程中只会匹配最新的 Provider,如果 MyContext 和 MyContext1 提供了相同的方法,则 C 组件只会选择 MyContext1 提供的方法。
默认值的作用?
如果匹配不到最新的 Provider 就会使用默认值,默认值一般只有在对组件进行单元测试(组件并未嵌入到父组件中)的时候比较有用。
使用useContext获取上下文
通过 createContext 创建出来的上下文对象,在子组件中可以通过 useContext 获取 Provider 提供的内容
代码语言:javascript复制const { fn, a, b } = useContext(MyContext);
可以发现useContext 需要将 MyContext 这个 Context 实例传入。
这种用法会存在一个比较尴尬的地方,就是父子组件如果不在一个目录中,如何共享 MyContext 这个 Context 实例呢?
一般这种情况下,可以通过 Context Manager 统一管理上下文的实例,然后通过 export 将实例导出,在子组件中将实例 import 进来。
示例:
创建一个contextmanager文件进行统一管理Context创建实例然后将其导出;
代码语言:javascript复制import React from 'react';
export const MyContext = React.createContext();
export const MyContext1 = React.createContext();
在需要使用到对应实例的组件中分别去将对应Context实例导入进去使用
代码语言:javascript复制import { useContext } from 'react';
import { MyContext1 } from '@/utils/contextmanager'
const Component = () => {
const { fn } = useContext(MyContext1);
return <>Component</>
}
createContext和useContext实现数据共享
例子:比如子组件中需要修改父组件的 state 状态
一般的做法是将父组件的方法比如 setXXX 通过 props 的方式传给子组件,而一旦子组件多层级的话,就要层层透传。
如果使用 Context 就可以避免这种层层透传
父组件Provider提供上下文value
代码语言:javascript复制import React, { useState } from 'react';
import Child from './Child';
import { MyContext } from '@/utils/contextmanager';
const Parent = (props = {}) => {
const [step, setStep] = useState(0);
const [count, setCount] = useState(0);
const [number, setNumber] = useState(0);
return (
<MyContext.Provider value={{ setStep, setCount, setNumber }}>
<A>
<Child step={step} number={number} count={count} />
</A>
</MyContext.Provider>
);
}
export default Parent;
子组件 useContext 使用上下文
代码语言:javascript复制import React, { useContext, memo } from 'react';
import { MyContext } from '@/utils/contextmanager';
const Child = memo((props = {}) => {
const { setStep, setNumber, setCount } = useContext(MyContext);
return (
<>
<p>step is : {props.step}</p>
<p>number is : {props.number}</p>
<p>count is : {props.count}</p>
<hr />
<div>
<button onClick={() => { setStep(props.step 1) }}>step </button>
<button onClick={() => { setNumber(props.number 1) }}>number </button>
<button onClick={() => { setCount(props.step props.number) }}>number step</button>
</div>
</>
);
});
export default Child;
效果
关于使用memo是为了说明这个写法在这里是多余的
memo的作用是为了减少组件重新render过程中导致组件的重复渲染,而得到性能上的提升。
但是 Context 发生的变化是无法通过 memo 进行优化的,这个大家需要知道的一个点。
使用useReducer优化Context复杂度
上面的示例虽然实现了多级组件方法共享,但是暴露出一个问题。
就是把所有的方法都放在了 MyContext.Provider.value 属性中传递,必然造成整个 Context Provider 提供的方法越来越多,让维护变的就更复杂了。
这里其实可以通过useReducer包装,通过dispatch去触发action进行数据更新,所以我们可以如下优化一下上面代码
父组件
代码语言:javascript复制import React, { useReducer } from 'react';
import Child from './Child';
import { MyContext } from '@/utils/contextmanager';
const initState = { count: 0, step: 0, number: 0 };
const reducer = (state, action) => {
switch (action.type) {
case 'step': return Object.assign({}, state, { step: state.step 1 });
case 'number': return Object.assign({}, state, { number: state.number 1 });
case 'count': return Object.assign({}, state, { count: state.step state.number });
default: return state;
}
}
const Parent = (props = {}) => {
const [state: { step, number, count }, dispatch] = useReducer(reducer, initState);
return (
<MyContext.Provider value={{ dispatch }}>
<Child step={step} number={number} count={count} />
</MyContext.Provider>
);
}
export default Parent;
子组件
代码语言:javascript复制import React, { useContext, memo } from 'react';
import { MyContext } from '@/utils/contextmanager';
const Child = memo((props = {}) => {
const { dispatch } = useContext(MyContext);
return (
<>
<p>step is : {props.step}</p>
<p>number is : {props.number}</p>
<p>count is : {props.count}</p>
<hr />
<div>
<button onClick={() => { dispatch({ type: 'setp' }) }}>step </button>
<button onClick={() => { dispatch({ type: 'number' }) }}>number </button>
<button onClick={() => { dispatch({ type: 'count' }) }}>number step</button>
</div>
</>
);
});
export default Child;
将state也通过Context传递给子组件
其实上面的例子,子组件获取父组件的 state 还是通过 props传递的,其实也会存在层层嵌套
如果将整个 state 通过 Context 传入就无需层层组件的 props 传递(如果不需要整个state,可以只将某几个 state 给 Provider)
优化后,父组件
代码语言:javascript复制import React, { useReducer, useCallback } from 'react';
import Child from './Child';
import { MyContext } from '@/utils/contextmanager';
const initState = { count: 0, step: 0, number: 0 };
const reducer = (state, action) => {
switch (action.type) {
case 'step': return Object.assign({}, state, { step: state.step 1 });
case 'number': return Object.assign({}, state, { number: state.number 1 });
case 'count': return Object.assign({}, state, { count: state.step state.number });
default: return state;
}
}
const Parent = (props = {}) => {
const [state, dispatch] = useReducer(reducer, initState);
const parentStepHandler = useCallback(() => {
dispatch({ type: 'step' })
}, [])
return (
<MyContext.Provider value={{ dispatch, state }}>
<button onClick={parentStepHandler}>parent step </button>
<Child />
</MyContext.Provider>
);
}
export default Parent;
优化后,子组件
代码语言:javascript复制import React, { useContext, memo } from 'react';
import { MyContext } from '@/utils/contextmanager';
const Child = memo((props = {}) => {
const { state: { step, number, count }, dispatch } = useContext(MyContext);
return (
<>
{console.log('[Child] RELOAD-RENDER')}
<p>step is : { step }</p>
<p>number is : { number }</p>
<p>count is : { count }</p>
<hr />
<div>
<button onClick={() => { dispatch({ type: 'setp' }) }}>step </button>
<button onClick={() => { dispatch({ type: 'number' }) }}>number </button>
<button onClick={() => { dispatch({ type: 'count' }) }}>number step</button>
</div>
</>
);
});
export default Child;
效果
直接使用父组件 state 的性能问题
注意看上面的动图,在点击子组件的 【number step】 按钮的时候,虽然 count 的值没有发生任何变化,但是一直触发[Child] RELOAD-RENDER 的打印,即使子组件是通过 memo 包装过的。
出现这个问题原因是 memo 只会对 props 进行浅比较,而我们直接将 state 注入到了组件内部,因此 state 的变化必然会触发[Child] RELOAD-RENDER,整个 state 变化是绕过了 memo。
使用useMemo方式来解决上面state透传性能问题
使用 useMemo 优化子组件渲染
代码语言:javascript复制import React, { useContext, useMemo } from 'react';
import { MyContext } from '@/utils/contextmanager';
const Child = (props = {}) => {
const { state: { step, number, count }, dispatch } = useContext(MyContext);
const Content = useMemo(() => {
return <>
{console.log('[Child] RELOAD-RENDER')}
<p>step is : { step }</p>
<p>number is : { number }</p>
<p>count is : { count }</p>
<hr />
<div>
<button onClick={() => { dispatch({ type: 'setp' }) }}>step </button>
<button onClick={() => { dispatch({ type: 'number' }) }}>number </button>
<button onClick={() => { dispatch({ type: 'count' }) }}>number step</button>
</div>
</>
}, [step, number, count, dispatch]);
return Content;
};
export default Child;
效果
通过上面效果可以看到,点击 number step 按钮不变的时候是不会再触发打印的,所以DOM是没有被重新渲染的。
猜你爱看