美文网首页
React中不常用的功能——Context

React中不常用的功能——Context

作者: 依然还是或者其他 | 来源:发表于2021-03-05 23:29 被阅读0次

    React中不常用的功能——Context

    Context

    React源码版本16.8

    基本用法

    跨层级通信 Context

    React.createContext创建context对象

    // 新建Context.js
    import React from "react";
    export const Context=React.createContext();
    

    Context.Provider父级创建provider传递参数

    <Context.Provider value={theme}>
        <ContextTypePage/>
    </Context.Provider>
    

    子组件消费

    • contextType
    //传递一个参数的情况
     import React, { Component } from 'react'
     import {Context} from "../Context"
     export default class ContextTypePage extends Component {
     static contextType=Context;
     render() {
         const {themeColor}=this.context;
         console.log("contextType",this.context)
         return (
             <div className="border">
                <span>我是ContextTypePage页面</span>父级传过来的颜色是——{themeColor}
            </div>
         )
     }}
    

    该 API 订阅单一 context

    • Consumer
    import React, { Component } from 'react'
    import { Context,UserContext } from '../Context'
    
    export default class ConsumerPage extends Component {
        render() {
            return (
                <div className="border">
                    我是ConsumerPage页面
                    <Context.Consumer>
                        {
                            theme=>{
                            return <div>页面父组件传过来的是——{theme.themeColor}
                              <UserContext.Consumer>
                                 {
                                    user=><p>我是传过来第二个参数——{user.name}                    </p>
                                 }
                            </UserContext.Consumer>
                            </div>
                            }
                        }
                    </Context.Consumer>
                   
                </div>
            )
        }
    }
    
    
    • useContext
     //传递两个参数情况
     import React from 'react'
     import {Context,UserContext}from '../Context'
     export default function useContextPage(props) {
     const theme=React.useContext(Context)
     const user=React.useContext(UserContext)
     return (
         <div className="border">
             <span>我是useContextPage页面</span>
             父级传过来的是——{theme.themeColor}
             <p>传过来的第二个参数:{user.name}</p>
         </div>
     )
     }
    
    

    useContext只可以用在函数组件中或者自定义hook,且第二个参数不会覆盖第一个

    大致原理

    ReactContext.js 中的createContext

    // createContext 创建一个Context ,即入口函数
    export function createContext<T>(
      defaultValue: T,
      calculateChangedBits: ?(a: T, b: T) => number,
    ): ReactContext<T> {
      if (calculateChangedBits === undefined) {
        calculateChangedBits = null;
      } else {
        //dev相关
      }
    
    
    //NOTE: 声明context
      const context: ReactContext<T> = {
        //Symbol 类型
        $$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.
        _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,
        //_contex 指向context本身
        _context: context,
      };
    
      let hasWarnedAboutUsingNestedContextConsumers = false;
      let hasWarnedAboutUsingConsumerProvider = false;
    
      if (__DEV__) {
        //dev相关
      } else {
        //Consumer 指向context本身
        context.Consumer = context;
      }
      
      //dev相关
    
      return context;
    }
    
    

    contextType原理

    ReactFiberClassComponent.js 中的constructClassInstance
    即Class 组件 构造函数初始化

    function constructClassInstance(
      workInProgress: Fiber,
      ctor: any,
      props: any,
      renderExpirationTime: ExpirationTime,
    ): any {
      let isLegacyContextConsumer = false;
      let unmaskedContext = emptyContextObject;
      let context = null;
      const contextType = ctor.contextType;
    
        //dev情况.....
        
        
      if (typeof contextType === 'object' && contextType !== null) {
        //若contextType是对象 且不为null 则将contextType赋值给this.context
        //NOTE: 读取contextType,赋值给context
        context = readContext((contextType: any));
      } else {
        unmaskedContext = getUnmaskedContext(workInProgress, ctor, true);
        const contextTypes = ctor.contextTypes;
        isLegacyContextConsumer =
          contextTypes !== null && contextTypes !== undefined;
        context = isLegacyContextConsumer
          ? getMaskedContext(workInProgress, unmaskedContext)
          : emptyContextObject;
      }
    
      // Instantiate twice to help detect side-effects.
      //dev情况.....
    
      const instance = new ctor(props, context);
      const state = (workInProgress.memoizedState =
        instance.state !== null && instance.state !== undefined
          ? instance.state
          : null);
      adoptClassInstance(workInProgress, instance);
    
        //dev情况.....
    
      // Cache unmasked context so we can avoid recreating masked context unless necessary.
      // ReactFiberContext usually updates this cache but can't for newly-created instances.
      if (isLegacyContextConsumer) {
        cacheContext(workInProgress, unmaskedContext, context);
      }
    
      return instance;
    }
    

    从上可知若contextType是对象 且不为null 则将contextType赋值给this.context

    Consumer原理

    从构造函数可以知晓Consumer跟Provider是指向同一个context的,所以实现了跨级访问

    useState原理

    export function useContext<T>(
      Context: ReactContext<T>,
      unstable_observedBits: number | boolean | void,
    ) {
      const dispatcher = resolveDispatcher();
      
      //dev.....
      
      return dispatcher.useContext(Context, unstable_observedBits);
    }
    
    
    function resolveDispatcher() {
      const dispatcher = ReactCurrentDispatcher.current;
      //.....
      return dispatcher;
    }
    

    在ReactFiberHooks.js中 声明了

    export type Dispatcher = {
      readContext<T>(
        context: ReactContext<T>,
        observedBits: void | number | boolean,
      ): T,
      useState<S>(initialState: (() => S) | S): [S, Dispatch<BasicStateAction<S>>],
      useReducer<S, I, A>(
        reducer: (S, A) => S,
        initialArg: I,
        init?: (I) => S,
      ): [S, Dispatch<A>],
      useContext<T>(
        context: ReactContext<T>,
        observedBits: void | number | boolean,
      ): T,
        //其他声明......
        //....
    };
    
    const HooksDispatcherOnMount: Dispatcher = {
      readContext,
    
      useCallback: mountCallback,
      useContext: readContext,
      useEffect: mountEffect,
      useImperativeHandle: mountImperativeHandle,
      useLayoutEffect: mountLayoutEffect,
      useMemo: mountMemo,
      useReducer: mountReducer,
      useRef: mountRef,
      useState: mountState,
      useDebugValue: mountDebugValue,
    };
    
    const HooksDispatcherOnUpdate: Dispatcher = {
      readContext,
    
      useCallback: updateCallback,
      useContext: readContext,
      useEffect: updateEffect,
      useImperativeHandle: updateImperativeHandle,
      useLayoutEffect: updateLayoutEffect,
      useMemo: updateMemo,
      useReducer: updateReducer,
      useRef: updateRef,
      useState: updateState,
      useDebugValue: updateDebugValue,
    };
    

    在HooksDispatcherOnMount或HooksDispatcherOnUpdate中,useContext实际调用的都是readContext

    ReactFiberNewContext.js中的readContext

    //NOTE: Context读取
    export function readContext<T>(
      context: ReactContext<T>,
      observedBits: void | number | boolean,
    ): T {
        //dev....
        //.....
    
      if (lastContextWithAllBitsObserved === context) {
        // Nothing to do. We already observe everything in this context.
      } else if (observedBits === false || observedBits === 0) {
        // Do not observe any updates.
      } else {
        let resolvedObservedBits; // Avoid deopting on observable arguments or heterogeneous types.
        if (
          typeof observedBits !== 'number' ||
          observedBits === MAX_SIGNED_31_BIT_INT
        ) {
          // Observe all updates.
          lastContextWithAllBitsObserved = ((context: any): ReactContext<mixed>);
          resolvedObservedBits = MAX_SIGNED_31_BIT_INT;
        } else {
          resolvedObservedBits = observedBits;
        }
    
        let contextItem = {
          context: ((context: any): ReactContext<mixed>),
          observedBits: resolvedObservedBits,
          next: null,
        };
    
        if (lastContextDependency === null) {
          invariant(
            currentlyRenderingFiber !== null,
            'Context can only be read while React is rendering. ' +
              'In classes, you can read it in the render method or getDerivedStateFromProps. ' +
              'In function components, you can read it directly in the function body, but not ' +
              'inside Hooks like useReducer() or useMemo().',
          );
    
          // This is the first dependency for this component. Create a new list.
          lastContextDependency = contextItem;
          currentlyRenderingFiber.contextDependencies = {
            first: contextItem,
            expirationTime: NoWork,
          };
        } else {
          // Append a new context item.
          lastContextDependency = lastContextDependency.next = contextItem;
        }
      }
      // 只有在React Native里边isPrimaryRenderer才会是false
      return isPrimaryRenderer ? context._currentValue : context._currentValue2;
    }
    

    readContext返回context._currentValue

    总结
    context实现跨级读取访问的根本性就是通过Context组件维护一个稳定对象,在对象内维护一个可变的_currentValue值,供Consumer访问

    参考:

    React源码
    React-Context-文档
    react组件化——context

    相关文章

      网友评论

          本文标题:React中不常用的功能——Context

          本文链接:https://www.haomeiwen.com/subject/vatjqltx.html