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 是纯函数;