React

React 语法

  • 标签必须闭合

  • 组件名必须是大驼峰命名

  • class 使用 className

  • 绑定事件使用 onClick={xxxFunc} ,注意与原生事件名 onclick=“xxxFunc()” 区分

  • jsx 根节点用 <> 来包裹

  • jsx 中使用 {} 包裹变量

  • const [count, setCount] = useStage(0) 声明一个变量,且只能在组件根级声明,不能在函数内部声明

    // 定义基础类型的响应式数据
    const [name, setName] = useState('xxx');
    // 改变时调用
    setName('ooo');
    // 定义引用类型的响应式数据
    const [userInfo, setUserInfo] = useState({name: 'xxx'});
    // 改变时需要 set 一个新对象
    const newUserInfo = {...userInfo, name: 'ooo'}; 
    setUserInfo(newUserInfo);
  • <Child count={count} onAbc="{() => {console.log('trigger abc')}}"> 传递数据/事件

  • setCount 触发的过程:trigger -> render(virtual dom) -> commit ,在一个事件循环里,多次执行 setCount ,只有最后一次生效,因为在 commit 之前,count 只有一个状态 (batchUpdate) 。通过 setCount(count => count+1) 可以实现在一个事件循环里多次更改 count 状态。

声明/改变变量

// 简单类型
const [count, setCount] = useStage(0)

function handleChange() {
  setCount(2)
}

// 复杂类型
const [info, setInfo] = useStage({name: 'xxx', sex: 1})

function handleChange() {
  const newInfo = {...info}
  newInfo.sex = 2
  setInfo(newInfo)
}

// 多次改变
const [count, setCount] = useState(0)
function handleClick() {
  setCount((count) => count + 1)
  setCount((count) => count + 1)
  setCount((count) => count + 1) // 最终结果为 3
}

immutable (不可变更数据) 编程规范

pnpm i use-immer 可以直接在开发阶段识别 immutable 数据被变更,避免编码过程中直接改变

import { useImmer } from 'use-immer'
const [info, setInfo] = useImmer({name: 'xxx'})
// 推荐更新数据的方式
setInfo((draft) => {
  draft.name = 'ccc'
})

Reducer 聚合函数

import { useReducer } from 'react'
// 1. 定义数据
const [inputValue, setInputValue] = useState('')
// 2. 定义 reducer 函数
function listReducer(state, action) {
  if(action.type === 'add') {
    // 5. 根据指令修改数据
    const newState = [...state, { id: action.value, value: action.value }]
    return newState
  }
  return state
}
const [list, disptchList] = useReducer(listReducer, []) // reducer 函数 | 初始值
// 3. 定义 action 发送改变数据的指令
const action = {
  type: 'add',
  value: inputValue // 定义的某个 state 值
}
// 4. dispatch 派发数据
disptchList(action)
  • immer 版本的 reducer
// 2. 定义 reducer 函数
function listReducer(draft, action) {
  if(action.type === 'add') {
    // 5. 根据指令修改数据
    draft.push({ id: action.value, value: action.value })
  }
  return state
}

context - 类似于 vue 的 provide/inject

// 1. 在 nameContext.ts 中定义 nameContext
import { createContext } from 'react' // createContext 定义上下文
const nameContext = createContext('') // 可以赋初始值
export default nmeContext
// 2. 在 App.tsx 中注入 nameContext
import nameContext from './nameContext'
function App(){
  return (
    <>
      // .Provider 是固定写法
      <nameContext.Provider value="Dell"> // value 可以设置值
        <Header/>
      </nameContext.Provider>
    </>
  )
}
// 3.在 Header.tsx 中使用
import { useContext } from 'react'
import nameContext from './nameContext'
function Header(){
  const name = useContext(nameContext) // 取 nameContext 中保存的值
  return (
    <div>{name}</div> // 显示 Dell
  )
}

children - 类似于 vue 的 slot

<Header>
  <div>额外内容</div>
</Header>
// Header.tsx
function Header({children}){ // 固定写 children
  return (
    <div>Header</div>
    <div>{children}</div> // 这里显示额外内容
  )
}

Ref - 用于获取 Dom 或组件,类似于 vue ref 或 refs

  • Ref 定义一个不需要驱动页面渲染的响应数据,场景为:在 setXxx 时,会重新执行函数,如果 function 中有定义的 let timer = null,那么每次 setXxx 时,都会重置 timer ,为了解决这个问题,可以引入 useRef :
import { useRef } from 'react'
// 定义
const timer = useRef(null)
// 使用
timer.current = 'xzc' // 在一个事件循环中 timer 不会被重置为 null
  • 获取 dom 元素
function App(){
  const extDiv = useRef(null) // 通过 extDiv.current 可以拿到 div 元素
  return (
    <>
      <Header/>
      <Context/>
      <div ref={extDiv}>这里是 context 外部的内容</div>
      <Footer/>
    </>
  )
}
  • 获取子组件中的 dom 节点,需要子组件支持。类似把父组件定义的 Ref 通过 props 传递到子组件
// 父组件中
function App(){
  const headerRef = useRef(null) // 通过 extDiv.current 可以拿到 div 元素
  return (
    <>
      <Header ref={headerRef}/>
    </>
  )
}
// 子组件中
import { forwardRef } from 'react'
export default forwardRef((props, ref) => { // 固定写法
  return (
    <div ref={ref}>Header</div>
  )
})

Effect

  • Effect 表示副作用,用于处理一些可能带来副作用的逻辑,例如异步请求;

  • Effect 是在 render 执行结束,页面更新之后,再执行的;例如在 effect 中可以拿到 ref dom 节点;

  • 严格模式下 effect 会执行 2 次,如果重复执行带来问题,通过 return 函数清理副作用来解决;

import { useEffect, useState } from 'react'
useEffect(() => {
  // 第二个参数是一个数组,如果是空数组,表示只有在第一次渲染的时候执行;
  return () => {
    // 更新时,先执行 return 函数清理副作用,再执行 useEffect 中的逻辑;
    clearInterval(timer)
  }
}, [])
const [count, setCount] = useState('')
useEffect(() => {
  // 如果有值,表示在这个值变化的时候执行;
}, [count])

useMemo - 类似 watch ,可以指定某些变量变化时执行逻辑

与 effect 区别:useEffect 是在 render 执行完执行,useMemo 是在 render 过程中执行的

import { useMemo, useState } from 'react'
const [ name, setName ] = useState('')
const [ search, setSearch ] = useState('')
const filterList = useMemo(() => {
  // 只有 search 变化时下面的逻辑才执行
  return list.filter(item => item.includes(search))
}, [search])
import { memo } from 'react'
// 使用 memo 定义的子组件,接收的 props 在父组件中变更时,才会重新渲染
const Child = memo(({name}) => {
  return (
    <div>{ name }</div>
  )
})

useCallback - 生成的变量在依赖不发生变化时,不重新生成,节约性能

import { useCallback, useState } from 'react'
const [content, setContent] = useState('')
const handleChange = useCallback((e) => {
  // 第二个参数为空,那么 render 执行过程中不会再生成 handleChange 函数
  setContent(e.target.value)
}, [])

useSyncExternalStore - 数据来自于外部系统时,建议使用

import { useSyncExternalStore } from 'react'
// 定义订阅函数
function subscribe(callback) {
  window.addEventListener('online', callback)
  window.addEventListener('offline', callback)
  return () => {
    window.removeEventListener('online', callback)
    window.removeEventListener('offline', callback)
  }
}
// callback 函数被调用时,执行 useSyncExternalStore 中的第二个参数函数
const isOnline = useSyncExternalStore(subscribe, () => window.navigator.onLine)

异步加载组件

import { Suspense, lazy } from 'react'

const Todo = lazy(() => import('./Todo'))
// Suspense 可以优化异步加载组件的交互,加载完成之前显示 fallback 中定义的 html 片段
function Card(){
  return (
    <Suspense fallback={<div>loading</div>}>
      <Todo />
    </Suspense>
  )
}

其他理解

  • react 的自定义事件实质上就是传递回调函数属性;

  • 每一次 state 被 set 的时候,都会执行一遍组件渲染函数;

  • 组件在节点上的位置发生变化,组件会被销毁,状态会被清空;如果只是样式变化,不会销毁;

  • 严格模式下,render 函数会执行 2 次,react 期望 render 是纯函数;