React笔记

React常用API及相关内容。

阅读量:875

点赞量:16

评论量:0
Learn
标签摘要
React常用API及相关内容。
正文

笔记记录的是React18版本。


创建React项目

1、使用CreateReactApp进行创建

npx create-react-app my-app

2、使用vite进行创建

# 以下只是事例,可以灵活使用
# 使用npm,之后再选择模板
npm create vite@latest
# 使用pnpm指定模板
pnpm create vite my-react-app --template react-ts

创建Root并渲染

使用创建工具生成React项目之后,会自动生成相关代码,在index.tsx中,代码如下:

const root = ReactDOM.createRoot(
  document.getElementById('root') as HTMLElement
);
root.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);

常用API


React.Component

React组件的基类,创建React类组件一般继承自此类。

class Welcome extends React.Component {
  render() {
    return <h1>Hello, {this.props.name}</h1>;
  }
}

React.PureComponent

与React.Component类似,但实现了shouldComponentUpdate方法,对props和state进行了浅比较。

若想自定义比较方法,可使用shouldComponentUpdate自定义。


React.memo

React提供的高阶组件,若组件在相同prop下渲染的结果相同,这可以使用此方法对组件进行缓存。可通过第二个参数自定义比较函数。

记录组件渲染结果,提高组件性能。比较函数返回true不更新,返回false更新。

function MyComponent(props) {
  /* 使用 props 渲染 */
}
function areEqual(prevProps, nextProps) {
  /*
  如果把 nextProps 传入 render 方法的返回结果与
  将 prevProps 传入 render 方法的返回结果一致则返回 true,
  否则返回 false
  */
}
export default React.memo(MyComponent, areEqual);

注意:

与 class 组件中 shouldComponentUpdate() 方法不同的是,如果 props 相等,areEqual 会返回 true;如果 props 不相等,则返回 false。这与 shouldComponentUpdate 方法的返回值相反。


React.createElement

React渲染函数,使用较少,一般直接使用jsx。

React.createElement('div', {onClick: () => console.log('点击了div')}, '点我');

React.cloneElement

克隆React元素。


React.isValidElement

判断一个对象是否是React元素。


React.Children

提供了用于处理 this.props.children 不透明数据结构的实用方法。【更多】


ReactDOM.createPortal(child, container)

将组件渲染到指定容器(类似于Vue中的Teleport组件)。虽然被渲染到了别的位置,但是节点行为与普通节点相同,包括事件的冒泡机制。

render() {
  return ReactDOM.createPortal(
    this.props.children,
    document.body
  );
}


ReactDOM.flushSync(callback)

强制 React 同步刷新提供的回调函数中的任何更新。这确保了 DOM 会被立即 更新。


React.createContext(defaultValue)

创建一个Context,只有当组件所处的树中没有匹配到 Provider 时,其 defaultValue 参数才会生效。

通过Context.Provider组件包裹使用value来传递数据。

通过compenent.contextType = Context或者static contextType = Context 来给组件绑定Context,组件中就可以使用this.context获取值了。

当有多个Context时,可以使用Context.Consumer组件来接收多个Context传递的值:{value => <Component val={value} />}此方法不需要上面的绑定了。

// 创建Content
export const TestContent = React.createContext('默认value')

// 祖先元素上层使用Provider包裹
render() {
  return (
    <>
      {/* 使用Provider,this.context将获取到传递的value */}
      <TestContent.Provider value='传递的value'>
        <Test1></Test1>
      </TestContent.Provider>
      {/* 不使用Provider,this.context将获取到默认值 */}
      <Test1></Test1>
    </>
  )
}

// 子孙元素通过 static contextType = TestContent 或者 Test1.contextType = TestContent来绑定Context,就可以在组件下使用this.context了。
export class Test1 extends React.Component {
  // 使用 static 这个类属性来初始化 contextType。
  static contextType?: React.Context<any> | undefined = TestContent;
  // ...
  componentDidMount() {
    console.log(this.context)
  }
  //...
}
// 或者使用 component.contextType初始化 contextType。
Test1.contextType = TestContent

当有多个Context时,可以使用Content.Consumer来接收,并通过props传递给子孙组件。(当然,此组件在只有一个Content时也可以使用。)

注意:使用Content.Consumer之后,就不需要在子孙组件下指定contextType了,因为数据是直接通过props传递的。

// 创建两个Content
export const TestContent = React.createContext('默认test')
export const Test1Content = React.createContext('默认test1')

// 祖先元素上层使用Provider包裹,使用Consumer接收
render() {
  return (
    <TestContent.Provider value='传递的test'>
      <TestContent.Consumer>
        {test => (
          <Test1Content.Provider value='传递的test1'>
            <Test1Content.Consumer>
              {test1 => (
                <Test1 test={test} test1={test1}></Test1>
              )}
            </Test1Content.Consumer>
          </Test1Content.Provider>
        )}
      </TestContent.Consumer>
    </TestContent.Provider>
  )
}

export class Test1 extends React.Component {
  // ...
  componentDidMount(): void {
    console.log(this.props.test, this.props.test1)
  }
  // ...
}


React.createRef

创建一个能够通过 ref 属性附加到 React 元素的 ref。

React 会在组件挂载时给 current 属性传入 DOM 元素,并在组件卸载时传入 null 值。ref 会在 componentDidMount 或 componentDidUpdate 生命周期钩子触发前更新。

不能在函数组件上使用,因为没有实例,但可以通过React.forwardRef高阶组件来实现函数组件上绑定ref

export class Test1 extends React.Component {
  ref: React.RefObject<HTMLInputElement>;

  constructor(props: any) {
    super(props)
    this.ref = React.createRef()
  }

  componentDidMount(): void {
    this.ref.current?.focus()
  }

  render(): React.ReactNode {
    return (
      <div>
        <input type="text" ref={this.ref} />
      </div>
    )
  }
}

组件中使用ref属性时,也可以传递一个回调函数,该函数接收一个节点实例参数。

refFn(e: HTMLInputElement) {
  // 将打印input元素
  console.log(e)
}

render(): React.ReactNode {
  return (
    <div>
      <input type="text" ref={this.refFn} />
    </div>
  )
}

React.forwardRef

创建一个React组件,这个组件能够将其接受的 ref 属性转发到其组件树下的另一个组件中。

// 将组件定义的ref转发到组件内的原生元素上。
export const Test2 = React.forwardRef((props: any, ref: React.LegacyRef<HTMLInputElement>) => {
  return (
    <input type="text" ref={ref} />
  )
})
  
// 将定义在高阶组件上的ref,转发到高阶组件所包裹的原组件上。
function logProps(Component: React.Component) {
  class LogProps extends React.Component {
    componentDidUpdate(prevProps) {
      console.log('old props:', prevProps);
      console.log('new props:', this.props);
    }

    render() {
      const {forwardedRef, ...rest} = this.props;

      // 将自定义的 prop 属性 “forwardedRef” 定义为 ref
      return <Component ref={forwardedRef} {...rest} />;
    }
  }

  // 注意 React.forwardRef 回调的第二个参数 “ref”。
  // 我们可以将其作为常规 prop 属性传递给 LogProps,例如 “forwardedRef”
  // 然后它就可以被挂载到被 LogProps 包裹的子组件上。
  return React.forwardRef((props, ref) => {
    return <LogProps {...props} forwardedRef={ref} />;
  });
}

React.lazy(() => import(''))

定义一个动态加载的组件。一般用于路由的懒加载上。

const SomeComponent = React.lazy(() => import('./SomeComponent'));

可使用React提供的React.Suspense组件,对加载时间白屏的处理。


React.startTransition(callback)

传递一个函数,用于执行非紧急的任务。将紧急任务和非紧急任务区分开,使UI更新更加人性化。

看下图input输入框,在未使用startTransition时,有很明显的卡顿,使用之后卡顿消失。

screenshots.gif

const list: string[] = []
for(let i = 0; i < 30000; i++) {
  list.push(String(i))
}

export class Test1 extends React.Component<{}, {
  keyword: string,
  showList: JSX.Element[]
}> {
  handleInputChangeBind: (e: any) => void;

  constructor(props: any) {
    super(props)

    this.state = {
      keyword: '',
      showList: []
    }

    this.handleInputChangeBind = this.handleInputChange.bind(this)
  }

  handleInputChange(e: React.ChangeEvent<HTMLInputElement>) {
    this.setState({
      keyword: e.target.value
    })
    // 处理不紧急的渲染
    React.startTransition(() => {
      this.filterList(e.target.value)
    })
    
  }

  // 过滤加高亮等复杂操作
  filterList(keyword: string) {
    this.setState({
      showList: list.filter(item => item.includes(keyword)).map(item => {
        const idx = item.indexOf(keyword)
        return (
          <div key={item}>
            {item.slice(0, idx)}
            <span style={{color: 'pink'}}>{item.slice(idx, idx+keyword.length)}</span>
            {item.slice(idx + keyword.length)}
          </div>
        )
      })
    })
  }

  render(): React.ReactNode {
    return (
      <div>
        <input type="text" onChange={this.handleInputChangeBind} />
        { this.state.showList }
      </div>
    )
  }
}

component.forceUpdate(callback)

强制组件更新,会调用组件的reader方法,但会跳过组件的shouldComponentUpdate方法。不会影响子组件的生命周期。



component.defaultProps

通过component.defaultProps={}定义默认props


this.setState

设置state,可传递一个对象,或者传递一个返回对象的函数,会与组件的state进行合并,此方法可能是同步执行,也可能是异步执行。


render prop

一个用于告知组件需要渲染什么内容的函数。可以使用render prop来实现组件逻辑的复用。可以配合高阶组件来实现一个不需要render prop的组件。(属性名并不一定非要用render)。

注意:

render prop 与 React.PureComponent 一起使用时要小心,传递render如果是一个函数,且是在jsx中创建的,则React.PureComponent将始终会更新。可以将函数单独提出来,再使用。

一般用于封装一些公共的逻辑。下面为官方案例:

class Cat extends React.Component {
  render() {
    const mouse = this.props.mouse;
    return (
      <img src="/cat.jpg" style={{ position: 'absolute', left: mouse.x, top: mouse.y }} />
    );
  }
}

class Mouse extends React.Component {
  constructor(props) {
    super(props);
    this.handleMouseMove = this.handleMouseMove.bind(this);
    this.state = { x: 0, y: 0 };
  }

  handleMouseMove(event) {
    this.setState({
      x: event.clientX,
      y: event.clientY
    });
  }

  render() {
    return (
      <div style={{ height: '100vh' }} onMouseMove={this.handleMouseMove}>

        {/*
          使用 `render`prop 动态决定要渲染的内容,
          而不是给出一个 <Mouse> 渲染结果的静态表示
        */}
        {this.props.render(this.state)}
      </div>
    );
  }
}

class MouseTracker extends React.Component {
  render() {
    return (
      <div>
        <h1>移动鼠标!</h1>
        <Mouse render={mouse => (
          <Cat mouse={mouse} />
        )}/>
      </div>
    );
  }
}

通过高阶组件实现一个不需要render props的功能复用。

function withMouse(Component) {
  return class extends React.Component {
    render() {
      return (
        <Mouse render={mouse => (
          <Component {...this.props} mouse={mouse} />
        )}/>
      );
    }
  }
}


过期的api

ReactDOM.render被ReactDOM.createRoot+root.render取代。

ReactDOM.hydrate被ReactDOM.hydrateRoot取代。

ReactDom.unmountComponentAtNode被root.unmount取代。

findDOMNode已被弃用,可以使用ref替代。


常用组件


React.Fragment

能够在不额外创建 DOM 元素的情况下,让 render() 方法中返回多个元素。可以简写成<></>

<React.Fragment>
  Some text.
  <h2>A heading</h2>
</React.Fragment>
<!-- 或者使用简写 -->
<>
  <li>Some text.</li>
  <li>A heading</li>
</>

React.StrictMode

使用严格模式,为其后代元素触发额外的检查和警告。仅在开发模式下运行;它们不会影响生产构建。

<React.StrictMode>
  <App />
</React.StrictMode>


React.Suspense

提供了一个组件,可以指定加载指示器(fallback属性,接收一个组件),以防其组件树中的某些子组件尚未具备渲染条件。

// 该组件是动态加载的
const OtherComponent = React.lazy(() => import('./OtherComponent'));

function MyComponent() {
  return (
    // 显示 <Spinner> 组件直至 OtherComponent 加载完成
    <React.Suspense fallback={<Spinner />}>
      <div>
        <OtherComponent />
      </div>
    </React.Suspense>
  );
}

React.Profiler

组件来检测包裹组件的性能,传递两个props(id,onRender),组件可以多次使用,嵌套使用,仅在开发模式有效,

function callback() {
  /*
    id, // 发生提交的 Profiler 树的 “id”
    phase, // "mount" (如果组件树刚加载) 或者 "update" (如果它重渲染了)之一
    actualDuration, // 本次更新 committed 花费的渲染时间
    baseDuration, // 估计不使用 memoization 的情况下渲染整棵子树需要的时间
    startTime, // 本次更新中 React 开始渲染的时间
    commitTime, // 本次更新中 React committed 的时间
    interactions // 属于本次更新的 interactions 的集合
  */
  console.log(arguments)
}
ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
  <Profiler id="Navigation" onRender={callback}>
    <App />
  </Profiler>,
)


Context.Provider

Context的提供者组件,通过value传递数据。

<Context.Provider value={value}>
  ...
</Context.Provider>


Context.Consumer

接收Context传递的值,用于解决有多个Context时的问题。

<Content.Consumer>
  {test => (
    <Test1 test={test}></Test1>
  )}
</Content.Consumer>


类组件的生命周期


现有的生命周期

  1. constructor():构造函数,组件实例化时调用
  2. render():渲染函数,初次挂载及后续更新时调用
  3. componentDidMount():组件挂载后调用
  4. componentDidUpdate():组件更新后调用
  5. componentWillUnmount():组件卸载前调用
  6. shouldComponentUpdate():设置组件是否需要更新,可通过对比props和state,返回一个Boolean类型的值。true更新,false不更新。如果只是简单的浅比较,可以让组件继承自React.PureComponent。(需要注意的是,对于数组或对象这种引用类型的值,改变时需要替换为新创建的值,而不要直接在原数据上修改,否则比较时就是自身与自身进行比较,组件不会更新。)
  7. static getDerivedStateFromProps():会在调用 render 方法之前调用,并且在初始挂载及后续更新时都会被调用。它应返回一个对象来更新 state,如果返回 null 则不更新任何内容。
  8. getSnapshotBeforeUpdate(): 在最近一次渲染输出(提交到 DOM 节点)之前调用。
  9. static getDerivedStateFromError():此生命周期会在后代组件抛出错误后被调用。
  10. componentDidCatch():此生命周期在后代组件抛出错误后被调用。

使用static getDerivedStateFromError()或componentDidCatch()两个生命周期的一个或两个,来实现一个错误边界组件。


过时的生命周期

UNSAFE_componentWillMount():在挂载之前被调用。

UNSAFE_componentWillReceiveProps():在已挂载的组件接收新的 props 之前被调用。

UNSAFE_componentWillUpdate():组件更新之前调用。


生命周期执行顺序

挂载:constructor、static getDerivedStateFromProps、render、componentDidMount。

更新:static getDerivedStateFromProps、shouldComponentUpdate、render、getSnapshotBeforeUpdate、componentDidUpdate。

卸载:componentWillUnmount。

错误处理:static getDerivedStateFromError、componentDidCatch。


Hooks

Hook是什么:Hook 是一个特殊的函数,它可以让你“钩入” React 的特性。


使用规则

1、只能在函数最外层调用 Hook。不要在循环、条件判断或者子函数中调用。

2、只能在 React 的函数组件或者自定义 Hook 中调用 Hook。不要在其他 JavaScript 函数中调用。

3、Hook在组件中可以多次使用。

因为React函数组件在组件更新时,是通过Hook的执行顺序来保持状态的,组件更新时,函数组件会再次执行,执行到Hook时,会读取创建时Hook对应名字的状态,忽略掉参数,从而保证状态不被重置。这就体现了类组件的优点了,组件更新时不再实例化类,使用原来的实例化,从而保留状态。


常用Hook


useState

使用方法:

const [state, setState] = useState(initialState)

定义一个state,initialState仅在函数组件初次渲染时有效,后续更新将被忽略。也可以传一个函数

const [state, setState] = useState(() => {
  const initialState = someExpensiveComputation(props);
  return initialState;
});

事例:

function Counter({initialCount}) {
  const [count, setCount] = useState(initialCount);
  return (
    <>
      Count: {count}
      <button onClick={() => setCount(initialCount)}>Reset</button>
      <button onClick={() => setCount(prevCount => prevCount - 1)}>-</button>
      <button onClick={() => setCount(prevCount => prevCount + 1)}>+</button>
    </>
  );
}


useEffect

使用方法:

useEffect(() => { ... return () => {}},[deps])

生命周期Hook,相当于componentDidMount、componentDidUpdate、componentWillUnmount,但不是同步调度。

同步调度请使用useLayoutEffect,此Hook的调度方式与上面的三个生命周期一致,而useEffect不会阻塞浏览器更新屏幕。

第一个参数为函数,在组件挂载及更新时调用,此函数可以再返回一个函数,会在组件将要卸载时调用。

第二个参数传递一个数组,指定哪些值变化时触发更新,可以通过设置一个空数组,来使此Hook仅在挂载时执行,在进入页面获取请求时可以用到。

在不传递第二个参数时,组件更新函数和卸载函数再每次组件更新都会执行。

useEffect(() => {
  // 创建及更新组件时订阅。
  const subscription = props.source.subscribe();
  return () => {
    // 卸载组件时清除订阅,此方法在每次更新前都会调用
    subscription.unsubscribe();
  };
});
useEffect(
  async () => {
    const data = await fetch('xxx');
    setDate(data)
  },
  // 第二个参数传递一个空数组,使此Hook仅在创建时执行一次
  [],
);
useEffect(
  async () => {
    const data = await fetch('xxx', {id: this.props.id});
    setDate(data)
  },
  // 第二个参数传递更新依赖的参数,仅在变化时执行Hook
  [this.props.id],
);


useLayoutEffect

使用方法:

useLayoutEffect(effect, deps)

其函数签名与 useEffect 相同,但它会在所有的 DOM 变更之后同步调用 effect。使用 useEffect 调度的 effect 不会阻塞浏览器更新屏幕,这让你的应用看起来响应更快。大多数情况下,effect 不需要同步地执行。在个别情况下(例如测量布局),有单独的 useLayoutEffect Hook 供你使用,其 API 与 useEffect 相同。


useContext

使用方法:

const value = useContext(Context)

获取Context.Provider组件传递的Value,使用此Hook后,在父组件value值变化时,此函数组件一定会触发更新,不管有没有使用React.memo或者shouldComponentUpdate。

// 祖先传递
<TestContent.Provider value='传递的test'>
  <Test2></Test2>
</TestContent.Provider>

// 子孙接收
const value = useContext(TestContent)
console.log(value)


useReducer

使用方法:

const [state, dispatch] = useReducer(reducer, initialArg, init)

useState 的替代方案。

它接收一个形如 (state, action) => newState 的 reducer,并返回当前的 state 以及与其配套的 dispatch 方法。

第二个参数为初始值,第三个参数为可选参数,是一个函数,通过接收第二个参数来返回一个新的数据作为初始值,一般用于初始值依赖prop。

// 处理函数
function reducer(state, action) {
  switch(action.type) {
    case 'increment':
      return { count: state.count + 1 }
      break;
    case 'decrement':
      return { count: state.count - 1 }
    default:
      return state;
  }
}

export const Test1 = () => {
  // 定义初始值,及处理函数
  const [state, dispatch] = useReducer(reducer, 0)
  return (
    <div>
      <p>{state.count}</p>
      <button onClick={() => dispatch({type: 'increment'})}>+</button>
      <button onClick={() => dispatch({type: 'decrement'})}>-</button>
    </div>
  )
}

export const Test2 = (props: {init: number}) => {
  // 初始值使用props传递的,使用初始函数对初始值处理一下。(仅做演示)
  const [state, dispatch] = useReducer(reducer, props.init, (initial) => {
    return {count: initial}
  })
  return (
    <div>
      <p>{state.count}</p>
      <button onClick={() => dispatch({type: 'increment'})}>+</button>
      <button onClick={() => dispatch({type: 'decrement'})}>-</button>
    </div>
  )
}


useMemo

使用方法:

const memoizedValue = useMemo(() => any, deps)

返回一个memoized值。依赖数据不变化时,创建函数就不会再次执行,直接返回缓存的数据。

export const Test2 = () => {
  const [count, setCount] = useState(0)
  const [count1, setCount1] = useState(0)
  // 仅count变化时传递的函数才会执行
  const newCount = useMemo(() => {
    console.log('执行了Memo函数')
    return count * 2
  }, [count])
  return (
    <div>
      count:{count}
      <p>count1:{count1}</p>
      newCount:{newCount}
      <button onClick={() => setCount(count+1)}>设置count</button>
      <button onClick={() => setCount1(count1+1)}>设置count1</button>
    </div>
  )
}


useCallback

使用方法:

const memoizedCallback = useCallback(fn, deps);

相当于 useMemo(() => fn, deps),返回一个 memoized 回调函数。

因函数组件更新时,函数组件会重复执行,那么在函数内部定义的函数将会重新创建。

这时候如果重新创建的函数传递给了子组件,将会导致子组件更新。

因为prop传递的函数已不是上次的那个函数了,使用此hooks可以解决这个问题。

依赖数据不变的时候,函数还是组件第一次创建的那个函数,就不会触发子组件的更新了。

(当然前提是子组件继承自React.PureComponent或者使用了shouldComponentUpdate做了prop的比较)。

export const Test2 = () => {
  const [count, setCount] = useState(0)
  const [count1, setCount1] = useState(0)
  
  // 仅当count改变时,传递的函数才会重新创建
  const render = useCallback(() => {
    console.log('执行了渲染函数')
    return <h1>{count}</h1>
  }, [count])
  

  return (
    <div>
      count:{count}
      <p>count1:{count1}</p>
      <Test3 render={render}></Test3>
      <button onClick={() => setCount(count+1)}>设置count</button>
      <button onClick={() => setCount1(count1+1)}>设置count1</button>
    </div>
  )
}

class Test3 extends React.PureComponent<{render: () => JSX.Element}, {}, any>{
  constructor(props: any) {
    super(props)
  }

  render(): React.ReactNode {
    return this.props.render();
  }
}


useRef

使用方法:

const refContainer = useRef(initialValue);

返回一个可变的 ref 对象,其 .current 属性被初始化为传入的参数(initialValue)。返回的 ref 对象在组件的整个生命周期内持续存在。当 ref 对象内容发生变化时,useRef 并不会通知你。变更 .current 属性不会引发组件重新渲染。

常见的用例就是命令式的访问子组件:

function TextInputWithFocusButton() {
  const inputEl = useRef(null);
  const onButtonClick = () => {
    // `current` 指向已挂载到 DOM 上的文本输入元素
    inputEl.current.focus();
  };
  return (
    <>
      <input ref={inputEl} type="text" />
      <button onClick={onButtonClick}>Focus the input</button>
    </>
  );
}

也可以使用ref.current来存储任何值


useImperativeHandle

使用方法:

useImperativeHandle(ref, createHandle, [deps])

可以让你在使用 ref 时自定义暴露给父组件的实例值。应当与 forwardRef 一起使用。

function FancyInput(props, ref) {
  const inputRef = useRef();
  useImperativeHandle(ref, () => ({
    focus: () => {
      inputRef.current.focus();
    }
  }));
  return <input ref={inputRef} ... />;
}
FancyInput = forwardRef(FancyInput);

这样就可以在不暴露子组件的情况下,对子组件进行操作了。


useDebugValue

使用方法:

useDebugValue(value, formatValueFn)

可用于在 React 开发者工具中显示自定义 hook 的标签。

useDebugValue(isOnline ? 'Online' : 'Offline');
useDebugValue(date, date => date.toDateString());


useDeferredValue

使用方法:

const deferredValue = useDeferredValue(value);

接受一个值,并返回该值的新副本,该副本将推迟到更紧急地更新之后。该 hook 与使用防抖和节流去延迟更新的用户空间 hooks 类似。useDeferredValue 仅延迟你传递给它的值。如果你想要在紧急更新期间防止子组件重新渲染,则还必须使用 React.memo 或 React.useMemo 记忆该子组件。

function Typeahead() {
  const query = useSearchQuery('');
  const deferredQuery = useDeferredValue(query);

  // Memoizing 告诉 React 仅当 deferredQuery 改变,
  // 而不是 query 改变的时候才重新渲染
  const suggestions = useMemo(() =>
    <SearchSuggestions query={deferredQuery} />,
    [deferredQuery]
  );

  return (
    <>
      <SearchInput query={query} />
      <Suspense fallback="Loading results...">
        {suggestions}
      </Suspense>
    </>
  );
}


useTransition

使用方法:

const [isPending, startTransition] = useTransition()

返回一个状态值表示过渡任务的等待状态,以及一个启动该过渡任务的函数。将紧急任务和非紧急任务区分开,使UI更新更加人性化。与useDeferredValue类似。

const list: string[] = []
for(let i = 0; i < 30000; i++) {
  list.push(String(i))
}

// 过滤加高亮等复杂操作
function filterList(keyword: string) {
  return list.filter(item => item.includes(keyword)).map(item => {
    const idx = item.indexOf(keyword)
    return (
      <div key={item}>
        {item.slice(0, idx)}
        <span style={{color: 'pink'}}>{item.slice(idx, idx+keyword.length)}</span>
        {item.slice(idx + keyword.length)}
      </div>
    )
  })
}

export function Test2 (){
  const [keyWord, setKeyWord] = useState('')
  const [showList, setShowList] = useState<JSX.Element[]>([])
  const [pending, startTransition] = useTransition()


  const handleInputChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
    setKeyWord(e.target.value)
    // 处理不紧急的渲染
    startTransition(() => {
      setShowList(filterList(e.target.value))
    })
  }, [])


  return (
    <div>
      <input type="text" value={keyWord} onChange={handleInputChange} />
      { !pending && showList }
    </div>
  )
}

效果参考上面的React.startTransition,只是多了个状态。


useId

使用说明:

const id = useId();

用于生成横跨服务端和客户端的稳定的唯一 ID 的同时避免 hydration 不匹配的 hook。

function NameFields() {
  const id = useId();
  return (
    <div>
      <label htmlFor={id + '-firstName'}>First Name</label>
      <div>
        <input id={id + '-firstName'} type="text" />
      </div>
      <label htmlFor={id + '-lastName'}>Last Name</label>
      <div>
        <input id={id + '-lastName'} type="text" />
      </div>
    </div>
  );
}


useSyncExternalStore

使用方法:

const state = useSyncExternalStore(subscribe, getSnapshot[, getServerSnapshot])

一个推荐用于读取和订阅外部数据源的 hook,其方式与选择性的 hydration 和时间切片等并发渲染功能兼容。


useInsertionEffect

同useEffect,区别在于:在组件更新前触发。


React Diff算法


算法依据

1、两个不同类型的元素会产生不同的树。

2、可以通过设置key属性标记哪些子元素在不同的渲染中可能是不变的。


比较过程

1、根节点变成不同类型的标签,则销毁旧树(componentWillUnmount),创建新树(UNSAFE_componentWillMount、componentDidMount)。

2、节点类型相同时,保留节点,仅对比改变的属性。

3、对比同类型的组件元素时,组件实例不变,以保持不同渲染时state的一致,将更新组件的props以保证与最新元素一致(UNSAFE_componentWillReceiveProps、UNSAFE_componentWillUpdate、componentDidUpdate、render)。

4、对子节点进行递归比较。同时遍历新旧虚拟DOM树的比较,生成一个mutation。这样对比会导致尾部新增元素时仅创建一个新增元素即可,但是首部新增元素时,就会重新创建所有子节点。可以通过key属性解决。

5、使用key属性来添加标记,优化比较,key应保证唯一性和不变性,尽量不要使用下标。


发布于: 3/9/2023, 9:43:32 AM
最后更新: 12/3/2024, 3:06:29 PM