阅读 23

React传-2

原文地址

Suspense 与 lazy

src/React.js里,有几个组件长得比较奇怪。

Fragment: REACT_FRAGMENT_TYPE,
Profiler: REACT_PROFILER_TYPE,
StrictMode: REACT_STRICT_MODE_TYPE,
Suspense: REACT_SUSPENSE_TYPE,
unstable_SuspenseList: REACT_SUSPENSE_LIST_TYPE,
复制代码

它们都是一个symbol常量,作为一种标识,现在暂时占个位。

直接看lazy代码

export function lazy<T, R>(ctor: () => Thenable<T, R>): LazyComponent<T> {
  // lazy的用法
  // lazy(()=>import('./xxx.js')) 这里的ctor指的就是懒加载函数
  let lazyType = {
    $$typeof: REACT_LAZY_TYPE, // 这里的$$typeof 并不是指createElement的$$type,整个对象是createElement返回对象的type字段
    _ctor: ctor,
    // React uses these fields to store the result.
    _status: -1, // 状态码
    _result: null, // 结果
  };

  if (__DEV__) {
    // 开发环境下如果设置了defaultProps和propTypes会进行警告,
    // 可能是因为懒加载组件并不能在开发环境中merge原有的defaultProps
    let defaultProps;
    let propTypes;
    Object.defineProperties(lazyType, {
      defaultProps: {
        configurable: true,
        get() {
          return defaultProps;
        },
        set(newDefaultProps) {
          warning(
            false,
            'React.lazy(...): It is not supported to assign `defaultProps` to a lazy component import. Either specify them where the component  is defined, or create a wrapping component around it.',
          );
          defaultProps = newDefaultProps;
          // Match production behavior more closely:
          Object.defineProperty(lazyType, 'defaultProps', {
            enumerable: true,
          });
        },
      },
      propTypes: {
        configurable: true,
        get() {
          return propTypes;
        },
        set(newPropTypes) {
          warning(
            false,
            'React.lazy(...): It is not supported to assign `propTypes` to ' +
              'a lazy component import. Either specify them where the component ' +
              'is defined, or create a wrapping component around it.',
          );
          propTypes = newPropTypes;
          // Match production behavior more closely:
          Object.defineProperty(lazyType, 'propTypes', {
            enumerable: true,
          });
        },
      },
    });
  }

  return lazyType;
}

复制代码

传入一个Thenable函数,Thenable代表promise,最终返回一个LazyComponent类型的组件。

createContext

context 提供了一种在 组件之间共享此类值 的方式,使得我们无需每层显式添加 props 或传递组件 ,就能够实现在 组件树中传递数据 。

使用方式

const { Provider,Consumer } = React.createContext('default');


class App extends React.Component {
  render() {
    return (
      <Provider value='Foo'>
        <Content />
      </Provider>
    );
  }
}

function Child(){
  return (
    <Consumer>
      {
        value => <p>{value}</p>
      }
    </Consumer>
  )
}
复制代码

React.createContext 的第一个参数是 defaultValue,代表默认值,除了可以设置默认值外,它还可以让开发者手动控制更新粒度的方式,第二个参数calculateChangedBits,接受 newValue 与 oldValue 的函数,返回值作为 changedBits,在 Provider 中,当 changedBits = 0,将不再触发更新,而在 Consumer 中有一个不稳定的 props,unstable_observedBits,若 Provider 的changedBits & observedBits = 0,也将不触发更新。

以下是完整的测试用例

// From React v16.11.0 release react-reconciler/src/__tests__/ReactNewContext-test.internal.js
it('can skip consumers with bitmask', () => {
  const Context = React.createContext({foo: 0, bar: 0}, (a, b) => {
    let result = 0;
    if (a.foo !== b.foo) {
      result |= 0b01;
    }
    if (a.bar !== b.bar) {
      result |= 0b10;
    }
    return result;
  });

  function Provider(props) {
    return (
      <Context.Provider value={{foo: props.foo, bar: props.bar}}>
        {props.children}
      </Context.Provider>
    );
  }

  function Foo() {
    return (
      <Context.Consumer unstable_observedBits={0b01}>
        {value => {
          ReactNoop.yield('Foo');
          return <span prop={'Foo: ' + value.foo} />;
        }}
      </Context.Consumer>
    );
  }

  function Bar() {
    return (
      <Context.Consumer unstable_observedBits={0b10}>
        {value => {
          ReactNoop.yield('Bar');
          return <span prop={'Bar: ' + value.bar} />;
        }}
      </Context.Consumer>
    );
  }

  class Indirection extends React.Component {
    shouldComponentUpdate() {
      return false;
    }
    render() {
      return this.props.children;
    }
  }

  function App(props) {
    return (
      <Provider foo={props.foo} bar={props.bar}>
        <Indirection>
          <Indirection>
            <Foo />
          </Indirection>
          <Indirection>
            <Bar />
          </Indirection>
        </Indirection>
      </Provider>
    );
  }

  // 表示首次渲染
  ReactNoop.render(<App foo={1} bar={1} />);
  // ReactNoop.yield('Foo'); 与 ReactNoop.yield('Bar'); 均执行了
  expect(Scheduler).toFlushAndYield(['Foo', 'Bar']);
  // 实际渲染结果校验
  expect(ReactNoop.getChildren()).toEqual([
    span('Foo: 1'),
    span('Bar: 1'),
  ]);
  
  // 仅更新foo,foo 的值为 2
  // 此时 a.foo !== b.foo,changedBits = 0b01,Provider 发生更新
  ReactNoop.render(<App foo={2} bar={1} />);
  expect(Scheduler).toFlushAndYield(['Foo']);
  expect(ReactNoop.getChildren()).toEqual([
    span('Foo: 2'),
    span('Bar: 1'),
  ]);

  // 仅更新 bar ,bar 的值为 2
  // a.foo === b.foo 不更新
  // a.bar !== b.bar changedBits = 0b01,Provider 发生更新
  ReactNoop.render(<App foo={2} bar={2} />);
  expect(Scheduler).toFlushAndYield(['Bar']);
  expect(ReactNoop.getChildren()).toEqual([
    span('Foo: 2'),
    span('Bar: 2'),
  ]);

  // 同时更新
  // a.foo !== b.foo ,并且 a.bar !== b.bar changedBits = 0b01,Provider 发生更新
  ReactNoop.render(<App foo={3} bar={3} />);
  expect(Scheduler).toFlushAndYield(['Foo', 'Bar']);
  expect(ReactNoop.getChildren()).toEqual([
    span('Foo: 3'),
    span('Bar: 3'),
    ]);
});
复制代码

源码

export function createContext<T>(
  defaultValue: T,
  calculateChangedBits: ?(a: T, b: T) => number,
): ReactContext<T> {
  if (calculateChangedBits === undefined) {
    calculateChangedBits = null;
  } else {
    if (__DEV__) {
      // 开发环境下判断是否函数
      warningWithoutStack(
        calculateChangedBits === null || typeof calculateChangedBits === 'function',
        'createContext: Expected the optional second argument to be a  function. Instead received: %s',
        calculateChangedBits,
      );
    }
  }

  const context: ReactContext<T> = {
    $$typeof: REACT_CONTEXT_TYPE,
    // 用来比对的函数
    _calculateChangedBits: calculateChangedBits,
    // 记录最新的context值,_currentValue和_currentValue2它们的值是一样的,用的地方会不一样
    _currentValue: defaultValue,
    _currentValue2: defaultValue,
    //用于跟踪此上下文当前支持多少个并发程序。例如并行服务器渲染。
    _threadCount: 0,
    Provider: (null: any),
    Consumer: (null: any),
  };
  // 将Provide的_context指向自身,而Consumer则直接指向自身,就可以直接通过_context和自身取到最新值
  context.Provider = {
    $$typeof: REACT_PROVIDER_TYPE,
    _context: context,
  };

  let hasWarnedAboutUsingNestedContextConsumers = false;
  let hasWarnedAboutUsingConsumerProvider = false;

  if (__DEV__) {
    // A separate object, but proxies back to the original context object for backwards compatibility. It has a different $$typeof, so we can properly warn for the incorrect usage of Context as a Consumer.
    // 在开发环境下,如果开发者使用了旧版本的createContext,将提醒开发者使用最新的方式。
    const Consumer = {
      $$typeof: REACT_CONTEXT_TYPE,
      _context: context,
      _calculateChangedBits: context._calculateChangedBits,
    };
    // $FlowFixMe: Flow complains about not setting a value, which is intentional here
    Object.defineProperties(Consumer, {
      Provider: {
        get() {
          if (!hasWarnedAboutUsingConsumerProvider) {
            hasWarnedAboutUsingConsumerProvider = true;
            warning(
              false,
              'Rendering <Context.Consumer.Provider> is not supported and will be removed in ' +
                'a future major release. Did you mean to render <Context.Provider> instead?',
            );
          }
          return context.Provider;
        },
        set(_Provider) {
          context.Provider = _Provider;
        },
      },
      _currentValue: {
        get() {
          return context._currentValue;
        },
        set(_currentValue) {
          context._currentValue = _currentValue;
        },
      },
      _currentValue2: {
        get() {
          return context._currentValue2;
        },
        set(_currentValue2) {
          context._currentValue2 = _currentValue2;
        },
      },
      _threadCount: {
        get() {
          return context._threadCount;
        },
        set(_threadCount) {
          context._threadCount = _threadCount;
        },
      },
      Consumer: {
        get() {
          if (!hasWarnedAboutUsingNestedContextConsumers) {
            hasWarnedAboutUsingNestedContextConsumers = true;
            warning(
              false,
              'Rendering <Context.Consumer.Consumer> is not supported and will be removed in ' +
                'a future major release. Did you mean to render <Context.Consumer> instead?',
            );
          }
          return context.Consumer;
        },
      },
    });
    // $FlowFixMe: Flow complains about missing properties because it doesn't understand defineProperty
    context.Consumer = Consumer;
  } else {
    // Provider 与 Consumer 使用同一个context里的 _currentValue,等同于上面
    // 当 Consumer 需要渲染时,直接从自身取得 context 最新值 _currentValue 去渲染
    context.Consumer = context;
  }

  if (__DEV__) {
    // 在开发模式下,会跟踪有多少个视图进行渲染,react不支持一个context渲染多个视图
   // 如果有多个渲染器同时渲染,这里置为null是为了检测
    context._currentRenderer = null;
    context._currentRenderer2 = null;
  }

  return context;
}
复制代码

forwardRef

forwardRef用来转发ref,前面说到过,createElement在创建ReactElement的时候回将ref过滤掉,而当custom组件是无法接收到ref,所以需要做一层转发。

使用方式

function App(){
  const inputRef = useRef()

  return (
    <>
      <MyInput ref={inputRef}></MyInput>
    </>
  )
}

const MyInput = React.forwardRef((props,ref)=>{
  return <input type="text" ref={ref}></input>
})
复制代码

这样就可以通过转发拿到input节点。

源码

export default function forwardRef<Props, ElementType: React$ElementType>(
  render: (props: Props, ref: React$Ref<ElementType>) => React$Node,
) {
  // 接收一个函数组件作为参数
  if (__DEV__) {
    // 必须将forwardRef放在最外层,例如memo(forwardRef((props,ref)=>{ ... }))是错误的
    if (render != null && render.$$typeof === REACT_MEMO_TYPE) {
      warningWithoutStack(
        false,
        'forwardRef requires a render function but received a `memo` ' +
          'component. Instead of forwardRef(memo(...)), use ' +
          'memo(forwardRef(...)).',
      );
    } else if (typeof render !== 'function') {
      // 只接受函数组件
      warningWithoutStack(
        false,
        'forwardRef requires a render function but was given %s.',
        render === null ? 'null' : typeof render,
      );
    } else {
      // forwardRef 接收的函数组件必须为2个或者0个(使用形参)参数
      warningWithoutStack(
        // Do not warn for 0 arguments because it could be due to usage of the 'arguments' object
        render.length === 0 || render.length === 2,
        'forwardRef render functions accept exactly two parameters: props and ref. %s',
        render.length === 1
          ? 'Did you forget to use the ref parameter?'
          : 'Any additional parameter will be undefined.',
      );
    }

    if (render != null) {
      // forwardRef 不支持转发defaultProps和PropTypes ,不能将带有propTypes的组件传给forwardRef
      warningWithoutStack(
        render.defaultProps == null && render.propTypes == null,
        'forwardRef render functions do not support propTypes or defaultProps.  Did you accidentally pass a React component?',
      );
    }
  }
  // 在dom里对REACT_FORWARD_REF_TYPE做一层转发
  return {
    $$typeof: REACT_FORWARD_REF_TYPE,
    render,
  };
}
复制代码

如果只看react部分的源码貌似没有什么头绪,因为大部分处理逻辑都在react-domreact-reconciler 里面.

memo

memo会返回一个纯化(purified)的组件MemoFuncComponent,它的作用等同于PureComponent,只不过前者用于函数组件,后者用于类组件。

function App(){
  const [value,setValue] = useState(0)
  return <MyInput value={value}></MyInput>
}

const MyInput = memo((props)=>{
  return <input value={props.value}></input>
})
复制代码

react会自动对props的值做Object.is比较,如果此次的值与上次的值不一样,则更新,否则不更新。也可以自定义比较方法,当compare函数返回true时代表更新,false则不更新。

const MyInput = memo((props)=>{
  return <input value={props.value}></input>
},(newProps,oldProps) => false) // 始终不会更新
复制代码

源码

export default function memo<Props>(
  type: React$ElementType,
  compare?: (oldProps: Props, newProps: Props) => boolean,
) {
  if (__DEV__) {
    // memo只接受函数组件
    if (!isValidElementType(type)) {
      warningWithoutStack(
        false,
        'memo: The first argument must be a component. Instead ' +
          'received: %s',
        type === null ? 'null' : typeof type,
      );
    }
  }
  return {
    $$typeof: REACT_MEMO_TYPE,
    type,
    compare: compare === undefined ? null : compare,
  };
}
复制代码

comparenull时代表默认Object.is比较。

关注下面的标签,发现更多相似文章
评论