React中不常用的功能——Context

2021-03-05  本文已影响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>

子组件消费

//传递一个参数的情况
 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

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>
        )
    }
}

 //传递两个参数情况
 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

上一篇下一篇

猜你喜欢

热点阅读