前言
useContext
是react很基础的一个hook api,在一些比较大型,复杂的项目中常用,通过使用 useContext
,你可以更轻松地在组件之间共享状态,避免了通过 props 层层传递数据的麻烦。这在大型应用程序中特别有用,因为它提供了一种更直观的全局状态管理方式。
useContext
是一个hook,但是没有单独使用场景,是需要搭配createContext
配合使用,所以在讲解useContext
的时候也会顺带一些createContext
和react构建过程一起讲解。
源码解析
js
const value = useContext(Context)
按照常规流程,调用HooksDispatcherOnMountInDEV.useContext 的方法,前置check检查方法,核心方法readContext ;
useContext不会创建hook对象,首先直接会读取值
js
const value = context._currentValue ;
const contextItem = {
context: context,
memoizedValue: value,
next: null
};
fiber.dependencies = {
lanes: 0,
firstContext: contextItem
}
return value;
firstContext是一个单向链表,后续的useContext 会往next挂载;
返回value值,原代码很简单,还需要配合createContext 使用;
mount阶段和update阶段的useContext的源码是一样的,因为只有读值,所有不存在差异化。
createContext源码解读
js
const SomeContext = createContext(defaultValue)
createContext
主要就是创建一个context上下文对象,
js
const context = {
$$typeof: REACT_CONTEXT_TYPE, //这个一个组件标签,在遍历标签的时候找到对应的方法
_currentValue: defaultValue,
_currentValue2: defaultValue,
_threadCount: 0,
Provider: null,
Consumer: null,
_defaultValue: null,
_globalName: null
};
context.Provider = {
$$typeof: REACT_PROVIDER_TYPE,
_context: context
};
const Consumer = {
$$typeof: REACT_CONTEXT_TYPE,
_context: context,
};
Object.defineProperties(Consumer, {
Provider: {
get() {
console.error(
'Rendering <Context.Consumer.Provider> is not supported and will be removed in ' +
'a future major release. Did you mean to render <Context.Provider> instead?',
);
return context.Provider;
},
set(_Provider) {
context.Provider = _Provider;
},
},
_currentValue: {
get() {
return context._currentValue;
},
set(_currentValue) {
context._currentValue = _currentValue;
},
},
_currentValue2: {
get() {
return context._currentValue2;
},
set(_currentValue2) {
context._currentValue2 = _currentValue2;
},
},
_threadCount: {
get() {
return context._threadCount;
},
set(_threadCount) {
context._threadCount = _threadCount;
},
},
Consumer: {
get() {
console.error(
'Rendering <Context.Consumer.Consumer> is not supported and will be removed in ' +
'a future major release. Did you mean to render <Context.Consumer> instead?',
);
return context.Consumer;
},
},
displayName: {
get() {
return context.displayName;
},
set(displayName) {
console.warn(
'Setting `displayName` on Context.Consumer has no effect. ' +
"You should set it directly on the context with Context.displayName = '%s'.",
displayName,
);
},
},
});
context.Consumer = Consumer;
context._currentRenderer = null;
context._currentRenderer2 = null;
return context;
上面是通过createContext
创建的对象,可以看出实际上导出三个组件,context可以作为一个组件,context.Consumer和context.Provider三个组件,context和context.Consumer是同一种类型组件,但是不推荐直接使用context写法,而且react内部也会报error,
首先看一下context.Consumer的写法,
js
<SomeContext.Consumer>
{
(context)=>(
<ChildComponent {...context} />
)
}
</SomeContext.Consumer>
提一点,SomeContext的作为组件的时候,首字母必须大写,这样react认为是自定义组件进行解析。
在遍历到SomeContext.Consumer组件中,子组件必须是个函数,也是调用readContext(context)
这个是react内部直接执行,就是Consumer自己执行了useContext方法了。
执行children函数
js
const newChildren = readContext(context);
const newChildren = children(newValue);
Consumer组件只有消费能力,是没办法作为生产者。
Consumer组件在class组件中比较常用,函数式组件中还是常用Provider组件,看一下Provider组件的写法:
js
<SomeContext.Provider value={newValue}>
<ChildContext onclick={onclick} />
</SomeContext.Provider>
在beginWork阶段,Provider的代码:
js
// providerType._context === context.Provider._context
providerType._context._currentValue = props.value;
将Provider的value属性更新到context的值,继续遍历子节点。
而在completeUnitOfWork 阶段,就是遍历完成阶段,会将defaultValue重新赋值给context._currentValue ,所以在update阶段中,在Provider的父组件中是没办法获取更新之后的value值。
所以在子组件中使用useContext
获取的value值是Provider更新之后的value。
总结
context部分react会在类组件中或者函数组件中都使用,这章只是讲解函数式组件的写法;
useContext
和createContext
为了解决深层次组件的传值的问题,本质上就是创建一个全局对象,在对象中保留值和组件标识符。
但是组件和hook都没有可以触发更新的方法,所以需要和其他方法或者hook搭配使用,解决可以更新。
补充
context会有组件标识,是不是也可以不使用context的组件也可以使用useContext方法,
js
const ThemeContext = createContext({context:'createContext'});
// child
function Child(){
const context useContext(ThemeContext)
return <div>{context.context}</div>
}
child组件不需要有context组件的包裹就可以直接使用了,只不过就是没有太多意义。