在react16.0之前,如果想要使用context,需要使用childContextTypes以及contextTypes
// 父组件
class TestContext extends Component {
state = {
childContext: "123"
}
constructor(props) {
super(props)
}
getChildContext() {
return {
value: this.state.childContext
}
}
handleChildContextChange = (e) => {
this.setState({
childContext: e.target.value
})
}
render() {
return (
<div>
<input type="text" value={this.state.childContext} onChange={this.handleChildContextChange}/>
{this.props.children}
</div>
)
}
}
// 子组件
class ChildOldApi extends Component {
render() {
return (
<p>childContext: {this.context.value}</p>
)
}
}
// 下面这两块是重点,必须要声明一下类型才能够使用
ChildOldApi.contextTypes = {
value: PropTypes.string
}
TestContext.childContextTypes = {
value: PropTypes.string,
}
export default () => (
<TestContext>
<ChildOldApi />
<ChildOldApi />
</TestContext>
)
这可谓是我刚接触react的时候的使用方法了,多年未用,都忘记了。这么做有点繁琐,还要去定义这个contextTypes,每个要用到context的子组件都需要定义一下,且这种context的定义方法对子孙组件的影响太大了,只要有更改,就会让子孙后代全部重新渲染,这就很影响性能。
所以,react团队在16.0之后更新了context的定义方式,用createContext,一下子就简单了许多
const { Provider, Consumer } = createContext(null);
/**
* 这里的Consumer内部是一个方法,接收的就是写在Provider上的value属性
* value可以写对象,也可以直接写值,都是对应的
*/
class ChildNew extends Component {
render() {
return <Consumer>{(value) => <p>我是新的:{value.val} {value.a}</p>}</Consumer>;
}
}
/**
* 这里使用起来就方便多了,直接用Provider标签将内容包起来,里面的所有元素都可以直接通过Consumer获取到
*/
class TestContext extends Component {
state = {
childContext: "123",
};
constructor(props) {
super(props);
}
handleChildContextChange = (e) => {
this.setState({
childContext: e.target.value,
});
};
render() {
return (
<Provider value={{
val: this.state.childContext,
a: 1
}}>
<div>
<input type="text" value={this.state.childContext} onChange={this.handleChildContextChange} />
<ChildNew />
</div>
</Provider>
);
}
}
对比一下16.0前后的版本,就会发现,更新后的context确实好用了许多,内部也对性能有了一些优化,这些优化得通过阅读update的源码才能知道他到底优化了哪些地方,这个后面再补吧。而在16.8之后,react HOOKS对context又进行了hooks封装,得到了 useContext 这个hooks方法。
// 这里稍微有点不一样的地方就是 useContext需要接受一个Context对象作为参数,所以这里没有用解构
const ContextHook = createContext(null);
/**
* 这里的使用方法就更加简洁了,直接通过解构的形式,就能获取到之前在Consumer里的接受到的值
*/
const ChildHooks = () => {
const { val, a } = useContext(ContextHook);
return (
<p>
我是hooks:{val}, a: {a}
</p>
);
};
/**
* 所以父节点也要有点调整
*/
class TestContext extends Component {
// ...
render() {
return (
<ContextHook.Provider
value={{
val: this.state.childContext,
a: 1,
}}
>
<input type="text" value={this.state.childContext} onChange={this.handleChildContextChange} />
<ChildHooks />
</ContextHook.Provider>
);
}
}
hooks就大大简化了代码,而且变得更加易读。
以上就是context近期的一些变化,以及使用方法。源码特别短,主要代码都在更新那一块,还没来得及读,后面理解了源码再来补充这块知识点吧。
// hooks useContext源码
export function useContext<T>(
Context: ReactContext<T>,
unstable_observedBits: number | boolean | void,
): T {
const dispatcher = resolveDispatcher();
return dispatcher.useContext(Context, unstable_observedBits);
}
// 简化后的createContext的源码
export function createContext<T>(
defaultValue: T,
calculateChangedBits: ?(a: T, b: T) => number,
): ReactContext<T> {
if (calculateChangedBits === undefined) {
calculateChangedBits = null;
}
const context: ReactContext<T> = {
$$typeof: REACT_CONTEXT_TYPE,
_calculateChangedBits: calculateChangedBits,
// As a workaround to support multiple concurrent renderers, we categorize
// some renderers as primary and others as secondary. We only expect
// there to be two concurrent renderers at most: React Native (primary) and
// Fabric (secondary); React DOM (primary) and React ART (secondary).
// Secondary renderers store their context values on separate fields.
/**
* 作为一种支持多个并发渲染器的解决方案,我们将一些渲染器归为主渲染器,另一些则归为次渲染器。
* 我们最多只期望有两个并发渲染器:React Native(主渲染器)和Fabric(副渲染器);
* React DOM (primary)和React ART (secondary)。
* 二级渲染器将它们的上下文值存储在不同的字段中。
*/
_currentValue: defaultValue,
_currentValue2: defaultValue,
// Used to track how many concurrent renderers this context currently
// supports within in a single renderer. Such as parallel server rendering.
// 用于跟踪此上下文当前在单个呈现程序中支持多少个并发呈现程序。比如并行服务器渲染。
_threadCount: 0,
// These are circular(通知)
Provider: (null: any),
Consumer: (null: any),
};
context.Provider = {
$$typeof: REACT_PROVIDER_TYPE,
_context: context,
};
context.Consumer = context;
return context;
}