React 随记
React(反应) 没有响应式的概念
useState 的两个功能 提供更新函数 缓存变量
useRef 更新时不会导致视图刷新 在'走出React(反应)' 与外部通信时(通常是不会影响组件外观的浏览器 API)使用 比如 定时器ID DOM(通过回调函数管理列表)
// Inside of React function useRef(initialValue) { const [ref, unused] = useState({ current: initialValue }); return ref; //直接修改原始值 也会被React记录 并且不会进入队列 而是立即改变 }
useMemo类似于Computed 用于缓存计算值 当依赖改变时才会计算新值 否则返回旧值 useCallback 用于缓存函数
React(反应)无法得到组件实例 只能得到组件内的元素的实例 且需要在组件的渲染函数外包裹一层forwardRef
使用useImperativeHandle来伪装引用对象 控制暴露内容
flushSync中包含的代码执行后立即同步更新 DOM
谨慎修改由React(反应)管理的DOM节点 可以安全地修改 React(反应) 不会更新的 DOM 部分
普通变量也可以在视图中显示
因为存在快照 所以任何变量都可以被比对是否存在变化 但只有可与视图刷新同步的变量才适合被更新 ref.current(当前) 、location.pathname都不适合被监听(计算依赖项是在渲染期间 后者会污染纯度) 、通过state计算得到的普通局部变量则是适合监听的
数组或对象必须整体更新 将原始数据视为只读 避免直接数据突变 immutable (vue的理念是mutable)
Immer提供的draft(草案)是一种会记录操作的代理,找出更改的部分,生成全新的对象
两个优点 1.可以让您保持数据的先前版本完整 并在以后重用它们 2.使得组件比较数据是否改变的成本非常低 (比较引用地址)
钩子只能在组件顶层调用 不能在循环或条件中
状态的定义需要反向排除考虑 并不是所有视图的需要的数据就定义为状态
纯函数的定义
不要在渲染期间更改外部对象或变量 从而导致全局突变 (局部的突变、访问外部的对象或变量都是被允许的 但是也可能污染纯度,不要执行这种污染纯度的行为)
不要在渲染期间访问Ref (更新不及时)
然而突变又是必要的 事件处理函数 用来执行副作用 它们在渲染期间不会运行(可以理解为脱离当前函数执行 ) 因此无需保持纯粹 任何导致突变的操作都优先在这里执行
如果您已用尽所有其他选项并且无法找到合适的事件处理程序, 可以使用useEffect(处理由渲染引起的副作用,是状态更改时重新渲染唯一的Trigger )
其在提交并绘制完成后执行 (useEffect的生命周期需要与组件的生命周期分开理解)
但是,这种方法应该是您的最后手段 (逃生窗口)
并不一定要依赖改变的时候才触发
具有“稳定标识”的数据不需要被依赖 props useState等都是不稳定的 useContext 应该也是不稳定的 避免监听没有缓存的函数或对象 因为每次渲染都会生成新的 也都是不稳定的 局部的ref以及ref.current(当前)、全局的变量是稳定的
(语法检查器会判断稳定性以及检查副作用内的每个响应值是否都被设置为依赖项 - 依赖项无法选择。必须包含全部,如果运行不正常 比如出现了死循环或预期外的执行 则考虑是否应该分离useEffect,或使用useEventEffect将函数隔离,或调整属性的位置以证明自己是稳定的)
事件处理函数 和 useEffect内的都是用来执行副作用的 主要用来处理逃逸(优先考虑 但不绝对 若不存在逃逸行为则可有更合适的方式来处理 不要单纯的将useEffect理解为watchEffect) 导致(全局)突变
watchEffect - 触发 -> 渲染 -> 执行
useEffect - 改变 -> 渲染 -> 比对 -> 执行
You Might Not Need an Effect – React(反应) !!重要 !!
为了帮助排查副作用有没有被及时清理 在开发环境下 组件mount(挂载)之后会remount一次 某些情况下的重复虽然用户不可见但对开发会产生一定的困扰 需要在生产环境或useEffect外去处理 比如 logVisit请求
每次useEffect重新运行以及组件卸载之前 都会执行cleanup
使用updater function(功能) 剔除依赖 (下面的例子中没有直接访问messages)
渲染可以随时发生,因此组件不应依赖于彼此的渲染顺序。
内联样式需要使用驼峰命名
当props需要更新时 必须请求父组件重新传递一个新的对象 而不是直接修改 旧的props将被遗弃和回收
不要将数字放在 &&
的左侧
JSX中的HTML直接视为对象处理(虚拟DOM)
Key不能更改或在渲染时生成 也不要用数组的索引(虽然这是不指定时的默认值)
创建初始渲染
const root = createRoot(document.getElementById( 'root' )) root.render(
);
每次触发渲染时都会生成(可交互的)组件快照(全新的props、事件处理程序以及局部变量) 并且会在React(反应)中存储 根据快照进行提交 并且用于比对以及下次的状态还原
状态并不存储在组件中 但是会随着组件的销毁而被清除
优先比较Key,当Key相同时(Key不是全局唯一,只用来指定在父级中的位置),基于UI树中的位置判断 而不是JSX标记中的位置 相同位置的相同类型的组件会被视为同一个组件 不会被重新创建
不同位置 同一位置
可以嵌套使用但不要嵌套定义组件 嵌套定义的组件函数在每次重新渲染时都是重新生成的 因此对应的组件不会保留数据
更新函数中通常获取到的是旧的状态 把计算后的值存入更新队列 并进行批处理后再提交 如果传入的是getter 这在队列中存储的是一个参数为当前状态的函数
updater function(功能) 在渲染期间运行 因此必须是纯粹的 且只返回结果 不要尝试在内部进行多余的行为 在严格模式下updater function(功能)会执行两次以验证是否为纯函数(但丢弃第二个结果)
useReducer用于合并数据处理逻辑,从而根据状态进行操作 useImmerReducer
与更新函数相同 也需要进入队列 在下次渲染中执行 因此需要是纯函数 这种特点也使得其不依赖于组件 易于测试
则分派一个 reset_form
操作比分派五个单独的 set_field
操作更有意义
import tasksReducer from './tasksReducer.js' ; let initialState = []; let actions = [ {type: 'added' , id: 1, text: 'Visit Kafka Museum' }, {type: 'added' , id: 2, text: 'Watch a puppet show' }, {type: 'deleted' , id: 1}, {type: 'added' , id: 3, text: 'Lennon Wall pic' }, ]; let finalState = actions.reduce(tasksReducer, initialState); const output = document.getElementById( 'output' ); output.textContent = JSON.stringify(finalState, null , 2);
useContext用于父组件向下面的组件树中任一级子组件提供数据
上下文没有提供者时 获取时将取得实例化时的默认值
避免矛盾(不可能同时存在)、冗余状态(可由计算得出、总是同时更新等)
避免重复引用状态,因为每次更新都是整体进行的,引用的状态可能会被抛弃
避免深度嵌套状态,嵌套本质可以理解为引用 优先考虑将嵌套扁平化 通过记录ID映射来生成结构
不要将props定义为状态,因为当传入的props改变时,状态并不会变化,除非我们需要忽略更新,只是用来初始化数据
自定义钩子内一般都会调用内置钩子 并且只有组件和自定义钩子内才能调用内置钩子 钩子始终以use开头
自定义钩子内共享的是逻辑而不是状态 每次执行都是独立的状态 如果需要共享 那么则可以在组件中传递
不要使用自定义钩子来封装生命周期函数
useSyncExternalStore 用于替代useEffect订阅外部事件、同步外部数据 优点是可以设定在外部API无法在服务端使用时返回的默认值
封装自定义钩子可以易于内部代码的升级 因为外部并不关注实现
可以将useEffect内的部分逻辑抽离到JS中 这样就划分出了边界 变成与外部系统的通信
父组件更新时会更新全部子组件 不会比对props 这是因为React无法确认我们写的确实是一个纯函数 且有时比对相较于重新渲染的代价更大
可以使用useMemo(Component) 让React认为这是一个Pure Component (相同输入相同输出) 这样只会在props、useContext的值(可以理解为是外部注入的)以及内部状态发生变化时才会更新