React学习笔记.md
React学习笔记.md
JunsReact 学习笔记
预备知识
ESLint 和 Prettier 配置
ESLint
配置自动检查插件pnpm install vite-plugin-eslint
,然后去 vite 的配置文件中导入使用,效果就是代码如果过不了 esint,网页直接弹窗报错 😐。我感觉不如直接编辑器中自动提示,之前在小红书实习的时候有配置过自动校验,这个可以研究一下 🧐。
Prettier
装插件就可以,配置看自己喜好
JSX
- jsx 和 React 是独立的,只不过经常放在一起使用
- jsx 需要编译才能被浏览器识别
function App() { |
- 一些和 html 的区别
- 标签名要小写
- 标签必须闭合
- class -> className, for -> htmlFor
- 非自定义属性用驼峰命名法,
data-id
这样的自定义属性是合法的 {}
里面可以写 js,但是不能写 if 和 for,以及对象函数等(这里可以研究一下为什么 🧐)- 根元素唯一,可以使用
<> </>包裹
,等同于<Fragment> </Fragment>,如果需要给它加属性的话,就不能写空标签了
样式
- 行内样式,略过 🥱
- 全局样式
index.css
,在一个 tsx 文件导入后会影响其他的文件 - 局部样式
index.module.css
,样式隔离,默认不支持驼峰访问,需要配置(可以研究底层实现 🤗)
import style from './assets/index.module.css' |
- 局部样式配置支持驼峰写法
// vite.config.js |
基础知识
事件操作
event 合成事件
React.MouseEvent<HTMLButtonElement, MouseEvent>
,events 相较于原生多了一些自定义的参数,访问原生可以e.nativeEvent
事件委托到容器元素,也就是挂载的 root,和性能有关(可以深入研究的点 🧐)
传参,可以使用高阶函数 / 箭头函数,一般推荐用箭头函数 🤠
import style from './assets/index.module.css' |
条件渲染
- js 中给一个变量赋值,然后渲染
&&
||
? :
,注意 0 的时候不对劲 😆
数组渲染
- for,while,但是麻烦
- map,推荐
- 循环需要加 key,有助于更新 dom,用唯一值,不建议用 index 值(面试考点,深入研究一下 🧐)
组件的点标记写法
import style from './assets/index.module.css' |
组件通信
父传子,props(略过 🥱)
插槽的话直接
props.children
通信添加默认值
- 使用 es6 的解构添加默认值
组件.defaultProps = { }
通信限定类型(js 的),
组件.propTypes = { }
,需要安装prop-types
模块,个人感觉可以抛弃了,直接上 ts 吧 🥸
组件必须是一个纯函数
- 只负责自己的任务,它不会更改在该函数调用前就已存在的对象或变量
- 输入相同,则输出相同。给定相同的输入,纯函数应总是返回相同的结果,使用严格模式检测不纯的计算(调用俩次函数)
状态 和 useState
2023.12.24 02:48 学到了 p22
什么是状态?
- 随时间变化的数据被称为状态(state),状态可以数据驱动视图,但是普通的变量不可以
useState
是可以创建修改状态的方法
状态是如何改变视图的
- 普通变量无法重新渲染 jsx
- state 状态可以重新触发函数组件
多状态是如何正确记忆的
- 在同一个组件的每次渲染中,
useState
都依托于一个稳定的调用顺序 - 所以不要在逻辑中调用
useState
,会改变内部的顺序
- 在同一个组件的每次渲染中,
什么是状态的快照以及快照的陷阱
- 原理是函数的闭包(待深入)
state
变量看着像 js 的变量,但是实际特性更像是一个快照。
状态队列和批处理
等待所有代码都运行完毕之后再处理
state
更新。队列都执行完毕后,再进行 UI 更新更新的函数写法,每次拿到的就都是更新之后的值了
const [count, setCount] = useState(0) setCount(count + 1) // 0 + 1 setCount(count + 1) // 0 + 1 setCount(c => c + 1) // 0 + 1 setCount(c => c + 1) // 1 + 1
- 其实`setState(x)`实际会像`setState(n => x)`一样运行,`setCount(count + 1)`其实就是`setCount(c => count + 1)`,没有调用入参而已
- 状态是不可变的
- 当修改状态的值没有改变的时候,函数组件不会重新渲染
- 所以不要去直接修改状态,而是通过 set 方法去修改
- 对象/数组的解决方案
- 避免使用会修改原数组的方法,推荐使用返回新值的方法,或者直接拷贝(深拷贝面试要点!)
- 深拷贝会带来性能问题,可以引入`immer`模块来减少性能消耗
- 惰性初始化值,`useState`可以传入一个函数,但是会每次都触发,有性能问题,所以可以直接写一个匿名函数来解决这个问题
- ```tsx
import { useState } from 'react'
function computed(n: number) {
console.log('computed函数被调用了') // 每次点击都会触发
return n + 3
}
function App() {
const [count, setCount] = useState(computed(0))
const [count, setConut] = useState(() => computed(0)) // 只有初始化的时候会调用一次
const handleClick = () => {
setCount(count + 1)
}
return <button onClick={handleClick}>{count}</button>
}
状态提升来解决共享问题
- 就是把子组件的状态提升到父组件中,通过
props
传递给子组件
- 就是把子组件的状态提升到父组件中,通过
状态的重置处理问题
组件被销毁时,状态会被重置
当组件位置没有发生变化时,状态会被保留
结构不同或者添加了 key,状态会重置
{ /* 组件的状态会被保留 */ } { isStyle ? <Counter style={{ border: '1px red solid' }} /> : <Counter /> } { /* 结构不同时会重置状态 */ } { isStyle ? ( <Counter style={{ border: '1px red solid' }} /> ) : ( <div> <Counter /> </div> ) } { /* 添加key也会重置状态 */ } { isStyle ? ( <Counter style={{ border: '1px red solid' }} key='counter1' /> ) : ( <Counter key='counter2' /> ) }
- 利用状态产生计算变量
- 类似`vue`中的`computed`
- 因为重新渲染组件的时候拿到的状态快照都是新的,所以直接用普通变量就可以了
- ```tsx
function App() {
const [count, setCount] = useState(0)
const doubleCount = count * 2
return (
<div className={style.box}>
<button onClick={() => setCount(count + 1)}>count: {count}</button>
<button>doubleCount: {doubleCount}</button>
</div>
)
}
实战:写一个todoList
组件
import { useMemo, useState } from 'react' |
Hooks 进阶
什么是 hooks
- 在
Reac
t 中,useState
以及任何其他以”use〞开头的函数都被称为Hook
(即钩子),所以Hooks
就是代表着 use 函数的集合,也就是钩子的集合 Hooks
其实就是一堆功能函数,一个组件想要实现哪些功能就可以引入对应的钩子函数,像插件一样非常的方便
ref 和 useRef
useRef(initialValue)
返回的是{ current: initialValue }
他是可以改变的,改变时也不会触发重新渲染
用法:
用ref
引用一个值做记忆功能
import { useRef, useState } from 'react' |
和useState
的区别
ref | state |
---|---|
useRef(initialValue) 返回的是{ current: initialValue } |
useState(initialValue) 返回的是state 变量的当前值和一个state 设置函数 |
更改时不触发重新渲染 | 更改时触发重新渲染 |
可变 —— 可以在渲染过程之外修改和更新ref.current 的值 |
不可变 —— 必须用提供的舍之函数来修改state 变量,然后排队重新渲染 |
在渲染过程中不要写入或读取 ref.current ,初始化除外。这使得组件的行为不可预测。 |
可以随时读取state 。但是每次渲染都有不变的state 快照 |
使用场景:定时器的清除
// 这样会导致多个定时器同时运行,无法正确清除 |
const [count, setCount] = useState(0) |
通过ref
操作DOM
function App() { |
在逻辑中通过ref
操控DOM
function App() { |
使用fowWardRef
转发ref
当组件添加ref
属性的时候,需要forwardRef
进行转发,forwardRef
让组件通过 ref
向父组件公开 DOM 节点
const MyInput = forwardRef<HTMLInputElement>((_, inputRef) => { |
useImperativeHandle 自定义ref
的暴露
其实就类似vue
的defineExpose
interface InputRef { |
副作用 和 useEffect
- 纯函数的概念
- 只负责自己的任务,它不会更改在该函数调用前就己存在的对象或变
- 输入相同,则输出相同。给定相同的输入,纯函数应总是返回相同的结果
- 副作用的概念
- 函数在执行过程中对外部造成的影响称之为副作用,例如:Ajax 调用,DOM 操作,与外部系统同步等
- 在 React 组件中,事件操作是可以处理副作用的,但有时候需要初始化处理副作用,那么就需要 useEffec 钩子
基本使用
function App() { |
可以指定依赖项,初始的时候所有的useEffect
都会触发,之后只有依赖更新的时候才会触发,内部其实是通过Object.is()
来判断是否改变的
当依赖项是空数组的时候,只会初始触发,更新不触发,无依赖项是每次更新触发
function App() { |
尽量在useEffect
内部定义函数
因为对于依赖是否改变是调用的Object.is()
,但是俩个函数的内存地址是不一样的Object.is(function(){}, function(){}) === false
,所以说定义在外部的函数作为依赖项,会导致每次都刷新。也可以用useCallback
来解决。
const [count, setCount] = useState(0) |
最好的解决办法就是把函数定义在useEffect
内部
const [count, setCount] = useState(0) |
清理操作的重要性
import { useEffect, useState } from 'react' |
初始化数据时,要注意清理操作,所以更简洁的方式是使用第三方的 hook
实验性的useEffectEvent
可以将非响应式逻辑提取到效果事件中
const onSomething = useEffectEvent(callback)
useLayoutEffect 同步执行状态更新
useEffect
是在渲染被绘制到屏幕之后执行的,是异步的
useLayoutEffect
是在渲染之后但在屏幕更新之前,是同步的
大部分情况下我们采用useEffect
,性能更好。
但当你的useEffect
里面的操作需要处理 DOM,并且会改变页面的样式,就需要用useLayoutEffect
,否则可能会出现闪屏问题
useInsertionEffect DOM 更新前触发
应用场景非常少,因为获取不到 DOM 元素,所以只在 CSS-in-JS 库中才会使用
useReducer统一的状态管理集合
是处理状态的另一种方式,可以把更改状态的逻辑集合起来
import { useReducer } from 'react' |
useContext
就类似vue
的provide
和inject
import { createContext, useContext } from 'react' |
简单的状态管理可以 Reducer
+Context
,复杂一点的可以用第三方库
Memo
在组件的属性保持不变时跳过重新渲染组件
const MemoizedComponent = memo(SomeComponent, arePropsEqual?) |
useMemo
在重新渲染之间缓存计算结果,类似于 vue 中的 computed,是一个响应式变量
const cachedValue = useMemo(calculateValue, dependencies) |
因为是否重新渲染根据的是Object.is()
,所以当渲染依据的值是数组的时候,每次渲染都会重新创建变量,内存地址不一样,则结果不同,所以回造成子组件的重新渲染
import { memo, useMemo, useState } from 'react' |
useCallback 缓存函数
相当于是useMemo
的一种特殊写法
const fn = useMemo( |
startTransition
在不阻止 UI 的情况下更新状态,一般用在非紧急任务
useTransition 和 useDeferredValue
useTransition
是一个让你在不阻塞 UI 的情况下来更新状态的 Hook,返回一个状态值表示过渡任务的等待状态,以及一个启动该过渡任务的函数useDeferredValue
接受—个值,并返回该值的新副本,该副本将推迟到更紧急地更新之后
const [isPending, startTransition] = useTransition() |
useId
生成唯一 id,每个地方不一样
结束
之后的是一些 antd 组件的使用和简单实现,还有 hooks 的一些库和自己封装,还有其他库的介绍,这里就直接不看了