跳至正文
Wener Notes

React FAQ

约 5 分钟阅读
标签:FAQ

如何设计组件

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

React Context

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

forwardRef Typescript 添加静态属性

  1. assign
TSX
const Layout = forwardRef(() => {
  return null;
});
Layout.displayName = 'MainLayout';
// 添加静态属性,TS 不会出错
export const MainLayout = Object.assign(Layout, { Slot });
  1. 定义属性
TSX
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. 直接执行

TypeScript
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

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

Cannot update a component while rendering a different component

TypeScript
// 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

TSX
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
<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

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 props

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

as props

  • 用于指定组件类型
  • 类似的 asChild - 不渲染组件,只传递 props
    • 也可以认为是 as=Fragment

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.

registry

TypeScript
addReact('YourComponentName', YourComponent);
define('YourComponentName', componentBlueprint);
getDefinition('YourComponentName');
getInstance('YourComponentName', componentID);
isDefined('YourComponentName');

NotFoundError: Failed to execute 'removeChild' on 'Node': The node to be removed is not a child of this node.

  • 替代 Fragment 为 div

Reference: Web Components & React

text
const Button = lazy(() => import('./components').then((module) => ({ default: module.Button })));

function BrickFlipbox() {
  return (
    <brick-flipbox class='demo'>
      <div>front</div>
      <div>back</div>
    </brick-flipbox>
  );
}

class XSearch extends HTMLElement {
  connectedCallback() {
    const mountPoint = document.createElement('span');
    this.attachShadow({ mode: 'open' }).appendChild(mountPoint);

    const name = this.getAttribute('name');
    const url = 'https://www.google.com/search?q=' + encodeURIComponent(name);
    ReactDOM.render(<a href={url}>{name}</a>, mountPoint);
  }
}
customElements.define('x-search', XSearch);
JavaScript
// 修改属性
React.cloneElement(child, {
  className: className || null,
});

Performance Resources

关联信息

反向链接、本文链接的其他页面和外部资料。

参考资料

GitHub16 条
其他外链11 条
最近更新commit 906e816编辑