跳到主要内容

React FAQ

提示

如何设计组件

  • 面向组件设计 - 组件驱动开发

React Context

  • Context.Provider value 修改需要 rerender 组件
  • 一般 Provider 都在比较上层,因此可能会有性能问题
  • 使用不会变的 value
    • 不将状态内容作为 value
    • 例如 使用 rxjs BehaviorSubject 作为 value 则可以避免变化,也能订阅变化
    • 可使用 zustand 替代

forwardRef Typescript 添加静态属性

  1. assign
const Layout = forwardRef(() => {
return null;
});
Layout.displayName = 'MainLayout';
// 添加静态属性,TS 不会出错
export const MainLayout = Object.assign(Layout, { Slot });
  1. 定义属性
export type Props = {};

export interface CompoundedComponent
extends React.ForwardRefExoticComponent<Props & React.RefAttributes<HTMLInputElement>> {
yourStaticFunctionOrSomethingLikeThat: () => void;
}

const Component = React.forwardRef<HTMLInputElement, Props>((props, ref) => (
<input ref={ref} {...props} />
)) as CompoundedComponent;

Component.yourStaticFunctionOrSomethingLikeThat = () => {};

StrictMode

  • 在 dev 时渲染两次来检测潜在问题 - Detecting unexpected side effects
    • constructor
    • componentWillMount (or UNSAFE_componentWillMount)
    • componentWillReceiveProps (or UNSAFE_componentWillReceiveProps)
    • componentWillUpdate (or UNSAFE_componentWillUpdate)
    • getDerivedStateFromProps
    • shouldComponentUpdate
    • render
    • setState
  • Ensuring reusable state - 模拟 umount、remount
    • componentDidMount
    • componentWillUnmount
    • useEffect
    • useLayoutEffect
    • useInsertionEffect

useEffect 无依赖 vs. 直接执行

const callbackRef = useRef(callback);

// 1. useEffect
useEffect(() => {
callbackRef.current = callback;
});

// 2. 直接赋值
callbackRef.current = callback;
  • 执行时机不同
    • useEffect 和其他 useEffect 是顺序执行的
      • 可能会延迟执行
    • 直接赋值会在最开始执行
      • 必然会执行

如何选择运行时框架

最早的 React 开发一般使用 CRA,但 CRA 基于 webpack,异常的慢,在 2021 年不再值得使用。


  • 选择依据
    • 单页面 - vite, nextjs
      • 复杂前端
      • 动态模块 - systemjs, dynamic import, esm
    • 多页面 - vite, nextjs, remix
      • 支持多页面 export
    • 单 HTML 入口 vs. 多 HTML 入口
      • vitejs 支持 多 HTMl 入口
      • nextjs 只支持 单 HTML 入口
    • 需要 SEO/SSR - nextjs, remix
    • 全栈 - nextjs, remix
    • 路由类型
      • 客户端控制 - SPA - vite
      • 服务端控制 - nextjs, remix
    • 网站内容类型
      • 管理后台 - 单页、复杂、CS 交互
      • 营销 - SEO、静态、增量
      • 电商 - 数据、SEO、静态
    • 部署方式
      • 静态 - 需要导出
      • 动态 - 启动服务
        • 部署 NodeJS 会比较麻烦 - node_modules 很大
      • Serverless
  • 现在 CS 混合渲染越来越多,值得尝试
  • React Server Components 也是一个趋势
    • 组建级动态

React Class Components vs Function Components

React.Component
import React from 'react';

class Hello extends React.Component<{ name: string }, { name: string }> {
static props = {
name: 'Wener',
};

static getDerivedStateFromProps({ name }) {
return { name };
}

constructor(props) {
super(props);
}

render() {
return <h1>Hello, {this.state.name}</h1>;
}

componentDidMount() {
console.debug(`componentDidMount`);
}

getSnapshotBeforeUpdate(prevProps: Readonly<{ name: string }>, prevState: Readonly<{ name: string }>): any {
return {};
}

componentDidUpdate(prevProps: Readonly<{ name: string }>, prevState: Readonly<{ name: string }>, snapshot?: any) {
console.debug(`componentDidUpdate`);
}

componentWillUnmount() {
console.debug(`componentWillUnmount`);
}

shouldComponentUpdate(
nextProps: Readonly<{ name: string }>,
nextState: Readonly<{ name: string }>,
nextContext: any,
): boolean {
return false;
}

/**
* ErrorBoundary - 仅 ClassComponent 支持
*/
componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
console.log(`componentDidCatch`);
}
}
React.FC
const HelloFC: React.FC<{ name: string }> = ({ name }) => {
const [state, setState] = useState({ name });
// getDerivedStateFromProps
useEffect(() => {
setState({ name });
}, [name]);
useEffect(() => {
console.debug('componentDidMount');
return () => {
console.debug('componentWillUnmount');
};
}, []);

// render
return (
<h1>
Hello, <input value={state.name} onChange={(e) => setState({ name: e.target.name })} />
</h1>
);
};
HelloFC.displayName = 'HelloFC';
HelloFC.defaultProps = { name: 'Wener' };

const HelloMemo = React.memo(HelloFC, (a, b) => {
// shouldComponentUpdate
return a.name === b.name;
});

shouldComponentUpdate for Function component

const HelloMemo = React.memo(HelloFC, (a, b) => {
// shouldComponentUpdate
return a.name === b.name;
});

Cannot update a component while rendering a different component

// from
if (storeRef.current && !isEqual(preloadRef.current, props)) {
console.debug(`DashboardStoreProvider: update preload`);
preloadRef.current = props;
storeRef.current.setState(props as any);
}

// to
useDeepCompareEffect(() => {
if (storeRef.current) {
storeRef.current.setState(props as any);
}
}, [props]);

上下文变化但不从新渲染

  1. 使用能够 selector 的状态管理库 - 允许读取部分状态
  2. 使用能区分 read 和 write 的库 - 允许独立更新

动态加载 script

react-helmet, next/head 支持 script 标签,但无法检测状态。 react-helmet 可以支持一个 onChangeClientState 来检测。

可以考虑 useScript 自行封装一个。

iframe

class A extends React.Component {
render() {
return <iframe src="https://www.youtube.com/embed/cWDJoK8zw58" />;
}
}
class B extends React.Component {
render() {
return <div dangerouslySetInnerHTML={{ __html: "<iframe src='https://www.youtube.com/embed/cWDJoK8zw58' />" }} />;
}
}

CSS in JS

  1. 动态注入 CSS
  • styled, jsx
  1. 预定义 Class
  • tailwind
  1. 动态 Class 注入 CSS

被注入 canvas

<html style="overflow:hidden" lang="en">
<canvas
style="inset: 0px; pointer-events: none; position: fixed; z-index: 1000000000;"
width="1920"
height="514"
></canvas>
<head></head>
</html>
  • 由 React Developer Tools 注入

Cannot assign to read only property '_status' of object

  • 可能 React.lazy 导致
  • 可能 隐藏了实际异常

Typescript 类型

  • React.JSX.Element
  • ReactElement
  • React.Element

空内容

  • React.Element
    • <React.Fragment />
    • <></>
  • ReactNode
    • false, null, undefined, true

TypeError: Cannot read properties of null (reading 'useRef')

Typescript

React.ButtonHTMLAttributes<HTMLButtonElement>;
React.HTMLProps<HTMLButtonElement>; // 类型范围被扩大,例如 button 的 type 变成 string
React.HTMLAttributes<HTMLDivElement>; // -> AllHTMLAttributes
React.ComponentProps<"div">; // =ComponentPropsWithRef -> React.JSX.IntrinsicElements
React.ComponentPropsWithoutRef<'div'>; // 推荐用法
React.ComponentPropsWithRef<'div'>; // 如果需要 forward

React.JSX.IntrinsicElements["button"]

ref as props

  • React 19 默认行为
  • 支持这样用
  • 遗留问题 - ref 可能需要特殊处理
    • class component
      • ref 指向 class instance
      • 2019 后推荐使用函数写组建
    • HOC
  • 部分场景存在 introspect ref 并做合并修改
  • 参考

Error: Maximum update depth exceeded. This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate. React limits the number of nested updates to prevent infinite loops.