alt=poster

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比较。