React 代码片段

React 代码片段

使用 PropTypesTypeScript 声明和验证组件属性的类型

PropTypes: PropTypes 是 React 提供的轻量级的类型检查机制。可在组件的 propTypes 属性中使用 PropTypes 对象来定义属性的类型和验证规则。PropTypes 是静态检查机制,在运行时进行验证,可捕捉开发过程中潜在错误。PropTypes 提供常见的类型检查器,如 string、number、array、object 等,及更复杂的检查器,如 shape、oneOfType

import PropTypes from 'prop-types';
 
function Welcome({ name }) {
  return <h1>Hello, {name}</h1>;
}
 
Welcome.propTypes = {
  name: PropTypes.string.isRequired
};
 
// others
Welcome.propTypes = {
  name: PropTypes.oneOfType([
    PropTypes.string,
    PropTypes.number
  ])
};
 
MyComponent.propTypes = {
  info: PropTypes.shape({
    age: PropTypes.number.isRequired,
    name: PropTypes.string.isRequired
  })
};

创建任意位置可点击的空div

<div style={{ width: '100%', height: '100%' }} onClick={() => navigate(Path.Home)}>
  &nbsp;
</div>

function reducer(state: State, action: Action): State {
  switch (action.type) {
    case 'add':
      return { ...state, total: state.total + action.amount };
    case ...
      ...
    default:
      return state;
  }
}
 
const [state, dispatch] = useReducer(reducer, initialState);
 
const [state, dispatch] = useReducer<Reducer<State, Action>>(
  reducer,
  initialState
);
 
dispatch({ type: 'add', amount: 2 });

There is no defined structure for an action, but it is common practice for it to contain a property, such as type, to specify the type of change. Other properties in the action can vary depending on the type of change


useRef常见用途是以命令式的方式访问 HTML 元素。JSX中HTML元素有ref属性,可将其分配给ref

function MyComponent() {
  const inputRef = useRef<HTMLInputElement>(null);
  function doSomething() {
    console.log(
      inputRef.current
    );
  }
  return <input ref={inputRef} type="text" />;
}

这里使用的 ref 称为 inputRef,并最初为 null。因此,它明确地赋予了 HTMLInputElement 类型,这是输入元素的标准类型。然后,在 JSX 中将 ref 分配给输入元素上的 ref 属性。然后可以通过 ref 对象的 current 属性访问所有输入框的属性和方法。

All the standard HTML elements have corresponding TypeScript types for React.

useEffect(() => {
  if (!loading) {
    addButtonRef.current?.focus();
  }
}, [loading]);

The optional chaining (?.) operator accesses an object's property or calls a function. If the object accessed or function called using this operator is undefined or null, the expression short circuits and evaluates to undefined instead of throwing an error.


const memoizedValue = useMemo(
  () => expensiveCalculation(a, b),
  [a, b]
);

使用 useMemo 可以缓存函数的计算结果,并在依赖项发生变化时重新计算。这样,只有在需要时才会进行计算,而不是每次组件重新渲染时都执行计算操作。


 const memoizedValue = useCallback<() => void>(
  () => someFunction (a, b),
  [a, b]
);

useCallback 的常见用途是防止不必要地重新渲染子组件

尽管这种重绘行为通常不会导致性能问题,但如果计算成本高昂的组件经常被重绘或具有缓慢副作用的组件经常被重绘,则可能导致性能问题。例如,在具有获取数据副作用的组件中避免不必要地进行多次重复呈现。

React 中有一个名为 memo 的函数可用于防止不必要的重新呈现。可以将 memo 函数应用于 ChildComponent,以防止不必要的重新渲染:

export const ChildComponent = memo(() => {
  return <span>A child component</span>;
});

memo 函数:

  • 使用场景:当你有一个函数组件,并且它的输出只受到组件的 props 变化的影响时,可以使用 memo 函数进行组件的记忆化。它将在组件 props 没有变化时,阻止不必要的重新渲染。
  • 工作原理:memo 函数会对组件进行浅比较,仅当组件的 props 发生变化时才会重新渲染。这对于那些具有稳定输入的组件(例如纯展示型组件)非常有用,可以避免不必要的渲染和重复计算。

useCallback 函数:

  • 使用场景:当你需要将一个回调函数传递给子组件,并且该回调函数依赖于某个值,但你又不希望在该值变化时重新创建回调函数时,可以使用 useCallback 函数。
  • 工作原理:useCallback 函数会返回一个记忆化的回调函数。它只会在依赖项发生变化时重新创建回调函数,并在其他情况下返回缓存的回调函数。这有助于避免不必要的函数创建,以提高性能。
import React, { useCallback } from 'react';
 
function ParentComponent() {
  const handleClick = useCallback(() => {
    // 处理点击事件
  }, []); // 依赖项为空数组,表示回调函数不依赖任何值
 
  return <ChildComponent onClick={handleClick} />;
}

usecallback 注册的函数只有在它的依赖项改变的时候才会重新创建,其所属组件重新渲染的时候不会重新创建


CSS 模块是一个 CSS 文件,文件名具有扩展名.module.css。这个特殊的扩展名允许 webpack 区分 CSS 模块文件和普通的 CSS 文件,以便它们可以被不同地处理。

CSS 模块(CSS Modules)是一种用于解决 CSS 类名冲突和作用域隔离的技术。它是在构建 Web 应用程序时用于管理 CSS 样式的一种模块化方案。

传统的 CSS 在全局作用域下工作,这意味着样式类名容易发生冲突,尤其是在大型项目中或多人协作开发时。这可能导致样式覆盖、命名冲突和不可预测的样式行为。

CSS 模块通过在构建过程中将类名进行哈希处理,从而实现样式的局部作用域和唯一性。它将 CSS 类名和对应的样式声明封装在一个模块中,确保模块内部的类名不会与其他模块冲突。

import React from 'react';
import styles from './MyComponent.module.css';
 
function MyComponent() {
  return <div className={styles.container}>Hello, CSS Modules!</div>;
}

生成的类名赋值给 styles.container。styles.container 是一个对象,它包含了处理后的唯一类名,可以直接应用于组件的 className 属性。


代码是一个 <Form> 组件,这是来自 Ant Design 这个 UI 框架的一个组件。这个组件的 onFinish 属性是一个函数,当表单的提交事件被触发时,它会被调用。

在你的代码中,你将 onSubmit 作为 onFinish 的处理函数。当表单被提交时,onFinish 会自动接收一个参数,这个参数是一个对象,它包含了表单中所有字段的值。因此,即使你在 <Form onFinish={onSubmit}> 中没有明确地传入参数,onSubmit 函数也会自动接收到这个参数。

所以,即使你没有明确地传入 "data" 参数,onSubmit 函数仍然能正常工作,因为在表单提交时,Ant Design 的 <Form> 组件会自动传递一个包含所有表单字段值的对象作为参数给 onFinish 属性的处理函数。


对于你的问题,React没有提供直接预渲染并缓存所有可能状态的机制。这是因为预渲染并缓存所有可能的状态,可能会导致内存占用过大,尤其是在状态数量庞大或组件结构复杂的情况下。而且,这种方法可能会产生不必要的计算,因为并不是所有的状态都会在实际运行中被访问到。

React的主要优化策略是减少不必要的重新渲染,而不是预渲染所有可能的状态。通过使用React.PureComponent,React.memo等机制,我们可以避免在状态没有改变的情况下,对组件进行无谓的重新渲染。


使用elementref来解决useref的类型问题

import { useRef, ElementRef } from "react";
 
const Component = () => {
  const audioRef = useRef<ElementRef<"audio">>(null);
 
  return <audio ref={audioRef}>Hello</audio>;
};

onClick = {async() => {}}的问题:

  1. 每次组件渲染时,会创建一个新的异步箭头函数,可能会导致额外的重渲染。
  2. 如果异步函数需要使用组件内的props或state,可能会读取到过期的闭包值。
  3. 如果传递给子组件,子组件可能会过于频繁渲染。
  4. 不利于测试和维护。

所以更好的方式是:

  1. 使用 useCallback 包装异步函数:
const handleClick = useCallback(async () => {
  // 异步代码
}, [deps])
 
return <Button onClick={handleClick} />

这样可以避免重复创建函数。

  1. 在组件内定义异步函数:
function Component() {
  const handleClick = async () => {
    // 异步代码
  }
  return <Button onClick={handleClick} />
}

{aa && <Component />}的问题:

link here

由于您的条件为假,因此不会返回第二个参数<GeneralLoader />,而是返回profileTypesLoading,它是一个数字。React会渲染它,因为React跳过对typeof boolean或undefined的任何内容进行渲染,并且会渲染typeof string或number的任何内容:

为了确保安全,您可以使用三元表达式{condition ? <Component /> : null}或将条件转换为布尔值{!!condition && <Component />}


useState是React Hooks中的一个函数,它允许在函数组件中添加state。

useState的实现位于react源代码的packages/react-reconciler/src/ReactFiberHooks.new.js文件中。

关键实现如下:

  1. useState内部会通过getCacheForType获取存储state的fiber对象:
function useState<S>(initialState: (() => S) | S) {
  const fiber = currentlyRenderingFiber;
  const hook = updateWorkInProgressHook();
  let baseState = hook.memoizedState;
  if (baseState === null) {
    // 初始化
    const queue = (hook.updateQueue: StateUpdateQueue<S>);
    const initialState = ((initialState: any): S);
    queue.shared.pending = {
      action: createStateAction(initialState),
      next: null,
    };
 
    // 通过getCacheForType获取存储state的fiber
    hook.memoizedState = baseState = initialState;
 
    queue.shared.effects = [];
  }
  return [hook.memoizedState, dispatchAction.bind(null, currentlyRenderingFiber)];
}
  1. getCacheForType会从Fiber节点上的cache对象中获取存储state的fiber:
function getCacheForType<T>(resourceType: () => T): T {
  let cache = currentCache;
  let tag = getCacheSignal();
  if (tag !== null) {
    let fiber = tag._reactInternals;
    let cacheType = ((resourceType: any): mixed);
    let key = getCacheKey(cacheType);
    let cacheItem = cache.cache.get(key);
    if (cacheItem === undefined) {
      cacheItem = fiber.memoizedState;
      cache.cache.set(key, cacheItem);
    }
    return (cacheItem: T);
  }
  // 没有cache,则创建
  let inst = resourceType();
  return inst;
}
  1. state的值通过hook.memoizedState存储在fiber对象上,并且会在组件重新渲染时从上一次的fiber对象中获取,实现状态的维护。

  2. 通过dispatchAction可以触发重新渲染,从而更新状态。

所以useState的关键逻辑就是通过fiber对象的cache存储状态,并在重新渲染时从上一次的fiber获取状态,来实现状态的维护。具体的逻辑可以通过阅读源码进一步理解。


useId是一个新的hook,可以为组件生成一个稳定可预测的id。

它可以帮助关联DOM元素与React组件,避免使用Math.random()产生冲突的id。


轻量组件(Light Components): 轻量组件目前还在提案阶段,旨在减少组件实例化的开销。

通过渲染静态版本避免创建组件实例,可以提升大型应用的性能。


React 组件通常包含了逻辑和展示两方面内容。所谓逻辑是指与 UI 无关的任何东西,比如 API 调用、数据处理和事件处理程序等。而展示部分则是指渲染过程中创建要显示在 UI 上的元素。

在 React 中,有一些简单而强大的模式被称为容器和展示模式,当我们创建组件时可以应用这些模式来帮助我们区分这两个关注点。


需要注意的是,在使用异步函数时,需要考虑到组件可能在异步操作完成之前就已经被取消挂载的情况。如果异步操作完成后试图更新一个已经被卸载的组件的状态,会导致React抛出一个警告。为了避免这个问题,可以在useEffect中使用一个取消标记,当组件卸载时设置该标记,然后在异步操作完成时检查该标记。

useEffect(() => {
    let cancel = false;
    const fetchData = async () => {
      const response = await fetch('https://api.example.com/data');
      const data = await response.json();
      if (!cancel) {  // 在更新状态之前检查取消标记
        setData(data);
      }
    };
 
    fetchData();
 
    return () => {
      cancel = true;  // 在组件卸载时设置取消标记
    };
  }, []);

<></>加key属性时,要写Fragment

{items?.map((item, index) => (
  <React.Fragment key={index}>
  </React.Fragment>
))}

在 React 中使用 localStorage 实现持久化确实可以工作,但是有一些需要注意的事项:

  1. 同步阻塞性localStorage 是一个同步 API,这意味着每次你读取或写入数据时,JavaScript 执行会暂停,直到操作完成。如果你存储的数据量很大,这可能导致页面响应延迟。
  2. 存储限制:存储空间限制在 5MB 左右
  3. 只能存储字符串
  4. 跨域问题localStorage 是按照域名分隔的,这意味着在不同的域名下,数据是隔离的。
  5. 隐私和安全问题:虽然 localStorage 是在客户端存储数据,但是它并非完全安全。比如,如果你的网站存在跨站脚本攻击(XSS)的漏洞,攻击者可能会读取或修改你的 localStorage 数据。因此,你不应该在 localStorage 中存储敏感信息,如密码或令牌。
  6. 服务器端渲染(SSR)中不可用