blog icon indicating copy to clipboard operation
blog copied to clipboard

React(16.8.6)源码之上下文传递(栈)

Open mengxiong10 opened this issue 6 years ago • 0 comments

上下文传递(栈)

React 的上下文传递都通过 valueStack 这个数组保存. 但是需要存储多种类型的数据(NewContext, HostContainer 等),配合游标(cursor)实现.

接口

  • createCursor(defaultValue)
  • isEmpty()
  • pop(cursor, fiber)
  • push(cursor, value, fiber)
// 游标的current保存当前的值;
// push的时候传入游标的值,索引+1, 游标的current再保存为最新传入的值;
// pop的时候取出valueStack当前位置的值交给游标, valueStack赋值null,索引-1;
// 这样相当于游标的值就是传统栈顶的值, 这样就不需要多个栈,  每次直接取游标的值就行了, 不用操作栈.
function createCursor(defaultValue) {
  return {
    current: defaultValue
  };
}

function pop(cursor, fiber) {
  cursor.current = valueStack[index];
  valueStack[index] = null;
  {
    fiberStack[index] = null;
  }
  index--;
}

function push(cursor, value, fiber) {
  index++;
  valueStack[index] = cursor.current;
  {
    fiberStack[index] = fiber;
  }
  cursor.current = value;
}

NewContext (新的context Api)

function pushProvider<T>(providerFiber: Fiber, nextValue: T): void {
  const context: ReactContext<T> = providerFiber.type._context
  push(valueCursor, context._currentValue, providerFiber)
  context._currentValue = nextValue
}

function popProvider(providerFiber: Fiber): void {
  const currentValue = valueCursor.current
  pop(valueCursor, providerFiber)
  const context: ReactContext<any> = providerFiber.type._context
  context._currentValue = currentValue
}

beginWork中遇到tag是ContextProvider时 pushProvider, 将value的值存在context._currentValue, 之后要 取context的值比如 useContext, 就是context._currentValue这个值.

为什么要将context入栈保存, 访问的不是context._currentValue吗?

如下考虑多个同样的context嵌套的情况. TestContext的value被改变2次, useContext(TestContext) 是取树中最近的 <TestContext.Provider>的值, 为了保证context._currentValue的值是正确的, 所以fiber的遍历顺序是深度优先, 在beginWork的时候入栈, 在completeWork的时出栈还原上一次的值. 保证每个组件会访问两次.

const TestContext = React.createContext<string>('');

function Test() {
  return (
    <TestContext.Provider value="before">
      <TestContext.Provider value="after">
        <Child />
      </TestContext.Provider>
      <Child />
    </TestContext.Provider>
  );
}

function Child() {
  const value = useContext(TestContext);
  return <span>{value}</span>;
}

mengxiong10 avatar May 16 '19 02:05 mengxiong10