React 笔记—高级指引

本文基于官方 v18.1.0 文档。

Code-Splitting

Code Splitting

You need to keep an eye on the code you are including in your bundle so that you don’t accidentally make it so large that your app takes a long time to load.

Code-Splitting is a feature supported by bundlers like Webpack, Rollup and Browserify (via factor-bundle) which can create multiple bundles that can be dynamically loaded at runtime.

Code-splitting your app can help you “lazy-load” just the things that are currently needed by the user, which can dramatically improve the performance of your app.

import()

The best way to introduce code-splitting into your app is through the dynamic import() syntax.

When Webpack comes across this syntax, it automatically starts code-splitting your app.

React.lazy

React 提供的一种异步动态加载机制。用 React.lazy 导入组件,然后在 Suspense 组件中使用组件。

The fallback prop accepts any React elements that you want to render while waiting for the component to load.

Avoiding fallbacks

介绍了 React 18 中并发特性 Transition 的一种用法,使用 startTransition 避免显示 fallback。

Route-based code splitting

一种基于路由拆分 bundle 的思路。用到了 React.lazyReact Router

Named Exports

React.lazy currently only supports default exports. If the module you want to import uses named exports, you can create an intermediate module that reexports it as the default.

Context

Context provides a way to pass data through the component tree without having to pass props down manually at every level.

Error Boundaries

Introducing Error Boundaries

Error boundaries are React components that catch JavaScript errors anywhere in their child component tree, log those errors, and display a fallback UI instead of the component tree that crashed.

Error boundaries do not catch errors for:

  • Event handlers
  • Asynchronous code (e.g. setTimeout or requestAnimationFrame callbacks)
  • Server side rendering
  • Errors thrown in the error boundary itself (rather than its children)

A class component becomes an error boundary if it defines either (or both) of the lifecycle methods static getDerivedStateFromError() or componentDidCatch(). Use static getDerivedStateFromError() to render a fallback UI after an error has been thrown. Use componentDidCatch() to log error information.

Only class components can be error boundaries.

New Behavior for Uncaught Errors

As of React 16, errors that were not caught by any error boundary will result in unmounting of the whole React component tree.

React 16 起,未捕获错误会导致整个 React 组件树被卸载。

…it is worse to leave corrupted UI in place than to completely remove it.

How About try/catch?

try / catch 只适用于命令式代码,而基于 jsx 的 React 是声明式的。

How About Event Handlers?

Error boundaries do not catch errors inside event handlers.

错误边界仅负责 React 组件本身的错误处理,比如 render 方法、其它生命周期方法。

Forwarding Refs

Ref forwarding is a technique for automatically passing a ref through a component to one of its children.

Ref forwarding is not limited to DOM components. You can forward refs to class component instances, too.

大致上就是,有时候组件使用者想获取组件内层某个 DOM 元素或子组件的 ref,因为 ref is not available in props,必须使用 React.forwardRef 创建组件,将 ref 作为函数参数传递到组件内部。

即,正常创建的组件 <FancyButton ref={ref} />,那 ref 一定是指向 FancyButton 的;而如果此组件是通过 React.forwardRef 创建,ref 就有可能指向内部元素。

这似乎意义不大啊,虽然 ref 是保留字,我依然可以换用其它更有意义的 prop 名字通过普通组件达到相同的目的,比如 <FancyButton innerButtonRef={ref} />
而且 ref 转发只适用于函数组件,不能用于类组件。

似乎只是提供了一种可以不修改接口就可以改变行为的手段,对组件库开发者比较有用?

Fragments

Fragments let you group a list of children without adding extra nodes to the DOM.

Keyed Fragments

Fragments declared with the explicit <React.Fragment> syntax may have keys.

短语法不能有属性。

Higher-Order Components

A higher-order component is a function that takes a component and returns a new component.

Use HOCs For Cross-Cutting Concerns

横切关注点指的是一些具有横越多个模块的行为。

高阶组件用来解决多组具有同一类切面导致的重复代码的问题。可以将形成切面关系的两者通过另一个更高阶的组件封装,将横切关注点放在高阶组件内。

A HOC is a pure function with zero side-effects.

Don’t Mutate the Original Component. Use Composition.

Convention: Pass Unrelated Props Through to the Wrapped Component

HOCs add features to a component. They shouldn’t drastically alter its contract. It’s expected that the component returned from a HOC has a similar interface to the wrapped component.

Static Methods Must Be Copied Over

在 HOC 返回前必须把 wrappedComponent 的所有静态方法复制到容器组件中,否则用户通过高阶组件调用不到这些静态方法。

However, this requires you to know exactly which methods need to be copied. You can use hoist-non-react-statics to automatically copy all non-React static methods.

Integrating with Other Libraries

以 jQuery 和 backbone.js 为例,详述了如何将 React 同其它库集成。略过没看。

JSX In Depth

Specifying The React Element Type

Capitalized types indicate that the JSX tag is referring to a React component.

React 组件名称必须大写字母开头。

React Must Be in Scope

Since JSX compiles into calls to React.createElement, the React library must also always be in scope from your JSX code.

只要用到了 React 组件,就要 import React from 'react';

If you don’t use a JavaScript bundler and loaded React from a <script> tag, it is already in scope as the React global.

Choosing the Type at Runtime

You cannot use a general expression as the React element type. If you do want to use a general expression to indicate the type of the element, just assign it to a capitalized variable first.

JavaScript Expressions as Props

if statements and for loops are not expressions in JavaScript, so they can’t be used in JSX directly.

String Literals

When you pass a string literal, its value is HTML-unescaped.

<MyComponent message="&lt;3" /> 这种会先被转义。

Props Default to “True”

If you pass no value for a prop, it defaults to true.

In general, we don’t recommend not passing a value for a prop, because it can be confused with the ES6 object shorthand {foo} which is short for {foo: foo} rather than {foo: true}.

Spread Attributes

You can also pick specific props that your component will consume while passing all other props using the spread syntax.

1
2
3
4
5
const Button = props => {
const { kind, ...other } = props;
const className = kind === "primary" ? "PrimaryButton" : "SecondaryButton";
return <button className={className} {...other} />;
};

变量 other 的定义。

Children in JSX

In JSX expressions that contain both an opening tag and a closing tag, the content between those tags is passed as a special prop: props.children.

Functions as Children

props.children works just like any other prop in that it can pass any sort of data, not just the sorts that React knows how to render.

Booleans, Null, and Undefined Are Ignored

false, null, undefined, and true are valid children. They simply don’t render.

Optimizing Performance

Virtualize Long Lists

使用一些第三方虚拟列表库:

Avoid Reconciliation

In most cases, instead of writing shouldComponentUpdate() by hand, you can inherit from React.PureComponent. It is equivalent to implementing shouldComponentUpdate() with a shallow comparison of current and previous props and state.

The Power Of Not Mutating Data

因为 React.PureComponent 做的是浅比较,所以要避免修改 object 类型的 state 或 prop 的值。
可以通过展开操作符或 object.assign 等方法每次创建新对象赋值给 state / prop。

Portals

Portals provide a first-class way to render children into a DOM node that exists outside the DOM hierarchy of the parent component.

Usage

1
2
3
4
5
6
7
8
render() {
// React does *not* create a new div. It renders the children into `domNode`.
// `domNode` is any valid DOM node, regardless of its location in the DOM.
return ReactDOM.createPortal(
this.props.children,
domNode
);
}

Event Bubbling Through Portals

This includes event bubbling. An event fired from inside a portal will propagate to ancestors in the containing React tree, even if those elements are not ancestors in the DOM tree.

Profiler API

onRender Callback

1
2
3
4
5
6
7
8
9
10
11
function onRenderCallback(
id, // the "id" prop of the Profiler tree that has just committed
phase, // either "mount" (if the tree just mounted) or "update" (if it re-rendered)
actualDuration, // time spent rendering the committed update
baseDuration, // estimated time to render the entire subtree without memoization
startTime, // when React began rendering this update
commitTime, // when React committed this update
interactions // the Set of interactions belonging to this update
) {
// Aggregate or log render timings...
}

React Without ES6

略过。

React Without JSX

略过。

Reconciliation

Motivation

React implements a heuristic O(n) algorithm based on two assumptions:

  1. Two elements of different types will produce different trees.
  2. The developer can hint at which child elements may be stable across different renders with a key prop.

Elements Of Different Types

Whenever the root elements have different types, React will tear down the old tree and build the new tree from scratch.

DOM Elements Of The Same Type

When comparing two React DOM elements of the same type, React looks at the attributes of both, keeps the same underlying DOM node, and only updates the changed attributes.

When updating style, React also knows to update only the properties that changed.

Recursing On Children

By default, when recursing on the children of a DOM node, React just iterates over both lists of children at the same time and generates a mutation whenever there’s a difference.

If you implement it naively, inserting an element at the beginning has worse performance.

如果没有 key,React 就无脑地迭代比较每一子项——即用索引匹配比较项。所以如果在索引 0 处新插入了项,React 会认为自 0 开始的每一项都改变了,而不认为只是新加了一项。借此说明 key 的必要性。

Keys

When children have keys, React uses the key to match children in the original tree with children in the subsequent tree.

key 只对 children 生效。

Reorders can also cause issues with component state when indexes are used as keys.

如果用索引做 key,重排序时比较项会错乱。

Tradeoffs

呼应开头的两个假设。为了能获得最好的性能,尽量使用同一种组件/元素类型;尽量使用稳定、可预测、唯一的 key 值。

Refs and the DOM

Accessing Refs

You may not use the ref attribute on function components because they don’t have instances.

Adding a Ref to a DOM Element

ref updates happen before componentDidMount or componentDidUpdate lifecycle methods.

Refs and Function Components

If you want to allow people to take a ref to your function component, you can use forwardRef.

承上文,ref 不能指向函数组件。

Callback Refs

ref 属性还支持接受一个函数作为参数值。函数接受一个指向当前组件/元素的参数。这时候就不需要 createRef 了,用普通的成员变量保存参数即可。

Caveats with callback refs

If the ref callback is defined as an inline function, it will get called twice during updates, first with null and then again with the DOM element. This is because a new instance of the function is created with each render, so React needs to clear the old ref and set up the new one.

Render Props

The term “render prop” refers to a technique for sharing code between React components using a prop whose value is a function.

Use Render Props for Cross-Cutting Concerns

More concretely, a render prop is a function prop that a component uses to know what to render.

Using Props Other Than render

In fact, any prop that is a function that a component uses to know what to render is technically a “render prop”.

children 也是。但是 children 还比较特殊,支持把函数定义放在组件内部。

上边 3 句话逐渐接近 render prop 的本质。

Be careful when using Render Props with React.PureComponent

内联函数传递的通病——每次渲染都会认为是新创建的函数。解决方法也是一样,改为组件类的实例方法。

Static Type Checking

介绍了 Flow、TypeScript 等几种静态检查工具、语言。略。

Strict Mode

Strict mode checks are run in development mode only; they do not impact the production build.

StrictMode currently helps with:

Identifying unsafe lifecycles

Warning about legacy string ref API usage

Warning about deprecated findDOMNode usage

Detecting unexpected side effects

Conceptually, React does work in two phases:

  • The render phase determines what changes need to be made to e.g. the DOM. During this phase, React calls render and then compares the result to the previous render.
  • The commit phase is when React applies any changes. (In the case of React DOM, this is when React inserts, updates, and removes DOM nodes.) React also calls lifecycles like componentDidMount and componentDidUpdate during this phase.

这章节信息量很大。列出了 render 阶段具体有哪些生命周期方法;v18 引入的并发模式有可能会导致多次调用 render 阶段的生命周期方法;严格模式会故意地两次调用这些生命周期方法以帮助识别非幂等代码导致的问题等。

Detecting legacy context API

Ensuring reusable state

In the future, we’d like to add a feature that allows React to add and remove sections of the UI while preserving state.

为了支持这个 feature,React 要多次 mount、unmount 组件,StrictMode 会人为地 unmount 然后重新 mount 组件以测试组件,发现问题。

Typechecking With PropTypes

To run typechecking on the props for a component, you can assign the special propTypes property.

PropTypes exports a range of validators that can be used to make sure the data you receive is valid.

When an invalid value is provided for a prop, a warning will be shown in the JavaScript console. For performance reasons, propTypes is only checked in development mode.

Requiring Single Child

1
2
3
MyComponent.propTypes = {
children: PropTypes.element.isRequired
};

MyComponent 只能包含一个元素。

Default Prop Values

1
2
3
Greeting.defaultProps = {
name: 'Stranger'
};

name prop 的默认值是 Stranger。

Function Components

必须在 export 之前定义 propTypes,所以要把 export default function 分开两句,中间定义 propTypes 属性。

Uncontrolled Components

Default Values

Changing the value of defaultValue attribute after a component has mounted will not cause any update of the value in the DOM.

The file input Tag

In React, an <input type="file" /> is always an uncontrolled component because its value can only be set by a user, and not programmatically.

Web Components

React and Web Components are built to solve different problems. Web Components provide strong encapsulation for reusable components, while React provides a declarative library that keeps the DOM in sync with your data.

评论