Серия исходных кодов React | Подробное объяснение React Context

React.js
Серия исходных кодов React | Подробное объяснение React Context

В настоящее время Context — это очень мощный API, который часто не используется напрямую. Большинство проектов не будут использовать его напрямуюcreateContextЗатем передайте данные ниже, но используйте стороннюю библиотеку (react-redux).

Подумайте, часто ли он используется в проекте@connect(...)(Comp)так же как<Provider value={store}><App /></Provider>?

Что такое контекст

Контекст предоставляет способ передачи данных между деревьями компонентов без ручного добавления свойств на каждый уровень компонентов.

Данные верхнего уровня, которые необходимо передать некоторому глубокому компоненту черезpropsПроходить слой за слоем было бы очень утомительно, использование Context позволяет избежать явного прохождения через дерево компонентов слой за слоем.props.

Пример использования контекста

import React, { Component, createContext, useConText } from 'react'
const ColorContext = createContext(null)
const { Provider, Consumer } = ColorContext

console.log('ColorContext', ColorContext)
console.log('Provider', Provider)
console.log('Consumer', Consumer)

class App extends Component {
  constructor(props) {
    super(props)
    this.state = {
      color: 'red',
      background: 'cyan',
    }
  }
  render() {
    return <Provider value={this.state}>{this.props.children}</Provider>
  }
}
function Article({ children }) {
  return (
    <App>
      <h1>Context</h1>
      <p>hello world</p>
      {children}
    </App>
  )
}
function Paragraph({ color, background }) {
  return (
    <div style={{ backgroundColor: background }}>
      <span style={{ color }}>text</span>
    </div>
  )
}
function TestContext() {
  return (
    <Article>
      <Consumer>{state => <Paragraph {...state} />}</Consumer>
    </Article>
  )
}

export default TestContext

Эффект страницы

РаспечататьColorContext,Provider,Consumer

createContext

// createContext 可以让我们实现状态管理
// 还能够解决传递 Props drilling 的问题
// 假如一个子组件需要父组件的一个属性,但是中间间隔了好几层,这就会出现开发和维护的一个成本。这时候就可以通过这个 API 来解决
function createContext(defaultValue, calculateChangedBits) {
  var context = {
    ?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,
    Consumer: null
  };

  // 以下的代码很简单,就是在 context 上挂载 Provider 和 Consumer,让外部去使用
  context.Provider = {
    ?typeof: REACT_PROVIDER_TYPE,
    _context: context
  };

  var Consumer = {
    ?typeof: REACT_CONTEXT_TYPE,
    _context: context,
    _calculateChangedBits: context._calculateChangedBits
  };

  context.Consumer = Consumer;
  context._currentRenderer = null;
  context._currentRenderer2 = null;
  return context;
}

существуетreactВ пакете сгенерировано всего несколько объектов, которые относительно просты Давайте посмотрим, где это вступает в игру.

существуетConsumer childrenОтладчик используется в анонимной функции.

Просмотр стека вызовов

в основномnewChildren = render(newValue);,newChildrenдаConsumerизchildrenВозвращаемое значение после вызова,renderто естьchildren,newValueОтProvider valueуступка имущества.

newProps

newValue

см. далееreadContextреализация

let lastContextDependency: ContextDependency<mixed> | null = null;
let currentlyRenderingFiber: Fiber | null = null;
// 在 prepareToReadContext 函数
currentlyRenderingFiber = workInProgress;


export function readContext<T>(
  context: ReactContext<T>,
  observedBits: void | number | boolean,
): T {
    let contextItem = {
      context: ((context: any): ReactContext<mixed>),
      observedBits: resolvedObservedBits,
      next: null,
    };

    if (lastContextDependency === null) {
      // 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;
    }
  }

  // isPrimaryRenderer 为 true,定义的就是 true
  // 实际就是一直会返回  context._currentValue
  return isPrimaryRenderer ? context._currentValue : context._currentValue2;
}

пропустить середину, последнее предложениеreturn context._currentValue,а также

Передайте его сверхуcontextЗначение получено

Я до сих пор не понимаю, почему контекст может передаваться с верхнего уровня на нижний, и я напишу статью, чтобы объяснить это после того, как ознакомлюсь с реализацией межкомпонентной передачи, 囧.

Контекст разработан, чтобы быть очень особенным

Provider Consumerдва свойства контекста.

  var context = {
    ?typeof: REACT_CONTEXT_TYPE,
    _currentValue: defaultValue,
    _currentValue2: defaultValue,
    Provider: null,
    Consumer: null
  };

Providerиз?typeofдаREACT_PROVIDER_TYPE, который поставляется с_contextатрибут, указывающий наcontextСам по себе, то есть его собственный сын имеет атрибут, указывающий на него самого! ! !

  context.Provider = {
    ?typeof: REACT_PROVIDER_TYPE,
    _context: context
  };

Consumerиз?typeofдаREACT_CONTEXT_TYPE, который также имеет_contextатрибут, а также его собственный сын имеет атрибут, указывающий на него самого! ! !

  var Consumer = {
    ?typeof: REACT_CONTEXT_TYPE,
    _context: context,
    _calculateChangedBits: context._calculateChangedBits
  };

Таким образом, вы можете сделать предположение,ProviderНовое значение, назначенное атрибутом value, должно пройти_contextатрибут переданcontextвыше, изменено_currentValue. такой же,Consumerтакже на основе_contextпонятноcontextиз_currentValue,Потомrender(newValue)воплощать в жизньchildrenфункция.

useContext

useContextЭто функция, предоставляемая хуками реагирования, которая может упростить получение контекста.

См. код ниже

import React, { useContext, createContext } from 'react'
const NameCtx = createContext({ name: 'yuny' })
function Title() {
  const { name } = useContext(NameCtx)
  return <h1># {name}</h1>
}
function App() {
  return (
    <NameCtx.Provider value={{ name: 'lxfriday' }}>
      <Title />
    </NameCtx.Provider>
  )
}
export default App

Мое начальное значение{name: 'yuny'}, который фактически переназначает{name: 'lxfriday'}, последняя страница показываетlxfriday.

Исходный код, связанный с useContext

Первый взглядreactэкспортируется в упаковкеuseContext

/**
 * useContext
 * @param Context {ReactContext} createContext 返回的结果
 * @param unstable_observedBits {number | boolean | void} 计算新老 context 变化相关的,useContext() second argument is reserved for future 
 * @returns {*} 返回的是 context 的值
 */
export function useContext<T>(
  Context: ReactContext<T>,
  unstable_observedBits: number | boolean | void,
) {
  const dispatcher = resolveDispatcher();
  return dispatcher.useContext(Context, unstable_observedBits);
}
// Invalid hook call. Hooks can only be called inside of the body of a function component. 
function resolveDispatcher() {
  const dispatcher = ReactCurrentDispatcher.current;
  return dispatcher;
}
/**
 * Keeps track of the current dispatcher.
 */
const ReactCurrentDispatcher = {
  /**
   * @internal
   * @type {ReactComponent}
   */
  current: (null: null | Dispatcher),
};

посмотриDispatcher, все, что связано с React Hooks.

опять такиreact-reconciler/src/ReactFiberHooks.jsв, естьHooksDispatcherOnMountInDEVа такжеHooksDispatcherOnMount,поясInDEVдолжен быть вdevelopmentОкружение будет использовать его, а тот, что без него, будет использоваться в `производстве.

const HooksDispatcherOnMount: Dispatcher = {
  readContext,

  useCallback: mountCallback,
  useContext: readContext,
  useEffect: mountEffect,
  useImperativeHandle: mountImperativeHandle,
  useLayoutEffect: mountLayoutEffect,
  useMemo: mountMemo,
  useReducer: mountReducer,
  useRef: mountRef,
  useState: mountState,
  useDebugValue: mountDebugValue,
};

HooksDispatcherOnMountInDEV = {
   // ... 
   useContext<T>(
      context: ReactContext<T>,
      observedBits: void | number | boolean,
    ): T {
      return readContext(context, observedBits);
    },
}

наверхуuseContextпроходить черезreadContextвозвращает значение контекста,readContextИсходный код описан выше.

стек вызовов просмотра отладчика

ИзначальноuseContext

существуетHooksDispatcherOnMountInDEVсередина

readContextсередина

После подробного анализа приведенного выше исходного кода вы должны понимать создание контекста и значение контекста Дизайн контекста действительно прекрасен! !


Добро пожаловать в мой публичный аккаунт Yunyingsky

Регулярный углубленный анализ исходного кода React