当前位置: 首页 > Web前端 > JavaScript

React中Context的变化及其背后的实现

时间:2023-03-27 00:27:38 JavaScript

Context在这篇文章中,我们说说Context。Context可以实现数据的跨组件传递。大多数时候,它是没有必要的,但有时,例如,如果用户设置了UI主题或区域偏好,如果从顶层向下传递到底层就有点麻烦了。最好直接使用Context来实现数据传递。OldContextAPI基本示例在说最新的API之前,我们先来回顾一下旧的ContextAPI:context.value}

}}//3.子组件添加contextTypes静态属性Child.contextTypes={value:PropTypes.string};classParentextendsReact.Component{state={value:'foo'}//1.当state或props发生变化时,会调用getChildContext函数getChildContext(){return{value:this.state.value}}render(){return(
)}}//2.给父组件添加childContextTypes静态属性Parent.childContextTypes={value:PropTypes.string};上下文中断问题对于这个API,React官方不推荐使用。对于可能出现的问题,React文档给出的介绍是:问题是如果组件提供的一个context发生变化,中间父组件的shouldComponentUpdate返回false,那么使用这个值的后代组件将不会更新。使用上下文的组件完全不受控制,因此基本上没有办法可靠地更新上下文。针对这个问题,我们来写个示例代码://1.子组件使用PureComponent>{this.context.theme}

}}GrandChild.contextTypes={theme:PropTypes.string};classParentextendsReact.Component{state={theme:'red'}getChildContext(){返回{theme:这个.state.theme}}render(){return({this.setState({theme:'blue'})}}>
)}}Parent.childContextTypes={主题:PropTypes.string};在此示例代码中,当单击红色文本时,文本不会更改为蓝色。如果我们把Child改成extendsComponent,说明可以正常修改当中间组件的shouldComponentUpdate为false时,Context的传递就会中断。PureComponent的存在是为了减少不必要的渲染,但是我们也希望Context能够正常传递。有什么办法解决吗?由于PureComponent的存在使得Context不再可更新,所以根本不应该更新它。Context不更新,GrandChild就不能更新了?当然有解决方案://1.创建一个订阅发布者,当然你也可以称之为依赖注入系统(dependencyinjectionsystem),简称DIclassTheme{constructor(value){this.value=valuethis.subscriptions=[]}setValue(value){this.value=valuethis.subscriptions.forEach(f=>f())}subscribe(f){this.subscriptions.push(f)}}classChildextendsReact.PureComponent{render(){return}}classGrandChildextendsReact.Component{componentDidMount(){//4.GrandChild获取到store后,订阅this.context.theme.subscribe(()=>this.forceUpdate())}//5.GrandChild从存储中获取所需的值render(){return

{this.context.theme.value}

}}GrandChild.contextTypes={theme:PropTypes.object};父类扩展React。组件{构造函数(p,c){超级(p,c)//2.我们实例化一个商店(想想redux的商店)并将它存储在实例属性中this.theme=newTheme('blue')}//3.将它传递给GrandChild组件getChildContext(){return{theme:this.theme}}render(){//6.通过store发布return({this.theme.setValue('red')}}>
)}}Parent.childContextTypes={theme:PropTypes.object};为了管理我们的主题,我们建立了一个依赖注入系统(DI),将store通过Context向下传递,这就需要使用store数据组件进行订阅,传入一个forceUpdate函数。当store发布后,每个依赖主题的组件执行forceUpdate,从而实现在不更新Context的情况下更新每个依赖组件。您可能还发现了,它有点react-redux的味道。当然,我们也可以使用Mobx来实现和简化代码。具体实现可以参考MichelWeststrate(Mobx的作者)如何安全使用Reactcontext新的ContextAPI基础示例想必大家都或多或少用过,我们直接上面的示例代码://1.CreateProviderandConsumerconst{Provider,Consumer}=React.createContext('dark');classChildextendsReact.Component{//3.Consumer组件接收一个函数作为子元素。此函数获取当前上下文值并返回一个React节点。render(){return({(theme)=>()})}}classParentextendsReact.组件{state={theme:'dark',};componentDidMount(){setTimeout(()=>{this.setState({theme:'light'})},2000)}render(){//2.通过Provider传值return()}}当Provider的值改变时,它里面的所有消费者组件都会被重新渲染。新API的好处是,从Provider到其内部消费者组件(包括.contextType和useContext)的传播不受shouldComponentUpdate函数的约束,因此消费者组件也可以在其祖先组件跳过更新时进行更新。模拟实现那么createContext是如何实现的呢?先不看源码。根据之前订阅发布者的经验,我们其实可以自己写一个createContext。让我们写一个尝试:f)}}functioncreateContext(defaultValue){conststore=newStore();//提供者类ProviderextendsReact.PureComponent{componentDidUpdate(){store.publish(this.props.value);}componentDidMount(){store.publish(this.props.value);}render(){返回this.props.children;}}//消费者类ConsumerextendsReact.PureComponent{constructor(props){super(props);this.state={value:defaultValue};store.subscribe(value=>{this.setState({值});});}render(){返回this.props.children(this.state.value);}}return{Provider,Consumer};}把React.createContext方法换成我们写的createContext,你会发现,也可以运行了。其实和解决老的ContextAPI的问题是一样的,只是多了一层封装。Consumer组件构建时订阅,Provider更新时发布,从而跳过PureComponent的限制,实现Consumer组件的更新。createContext源码现在我们去看看真正的createContext源码,源码位置在packages/react/src/ReactContext.js,简化后的代码如下:import{REACT_PROVIDER_TYPE,REACT_CONTEXT_TYPE}fromdefault'shared/ReactSymbolsContent'(export){constcontext={$$typeof:REACT_CONTEXT_TYPE,//作为支持多个并发渲染器的解决方法,我们将//一些渲染器归类为主要渲染器,其他渲染器为次要渲染器。我们只希望//最多有两个并发渲染器:ReactNative(主要)和//Fabric(次要);ReactDOM(主要)和ReactART(次要)。//辅助渲染器将它们的上下文值存储在单独的字段中。_currentValue:defaultValue,_currentValue2:defaultValue,//用于跟踪此上下文当前//在单个渲染器中支持多少个并发渲染器。比如并行服务器渲染。_threadCount:0,//这些是循环的Provider:null,Consumer:null,//添加这些到在VM中使用相同的隐藏类作为ServerContext_defaultValue:null,_globalName:null,};context.Provider={$$typeof:REACT_PROVIDER_TYPE,_context:context,};context.Consumer=context;returncontext;}你会发现,和上一篇文章涉及的源码是一样的。React的createContext只是返回一个数据对象,但没有关系。在以后的文章中,我们会慢慢分析实现过程。React系列讲解了React源码和ReactAPI背后的实现机制。ReactisthebestPractice,React的发展与历史等,预计50篇左右,欢迎关注,如果喜欢或者有启发,欢迎star,也是对作者的鼓励。