function createIncrement(incBy) {
let value = 0;
function increment() {
value += incBy;
console.log(value)
const message = `Current value is ${value}`;
return function log() {
console.log(message);
}
}
return increment
}
const c = createIncrement(1);
const log = c(); // 1
c(); // 2
c(); // 3
// Does not work!
log(); // "Current value is 1"
解决:
1、重新获取 const latestLog = c()(最新的 log)
2、把 const message 放到 log 里(最新的 value)
return function log() {
const message = `Current value is ${value}`;
console.log(message);
}
title: React 常用 Hooks date: 2024-04-17 20:54 updated: 2024-04-17 20:54 cover: //cdn.wallleap.cn/img/pic/cover/202302ihq49n.jpg category: 技术杂谈 tags:
前端 description: React 常用 Hooks
在函数组件中可以通过 Hook 使用不同的 React 功能,内置 Hook 或自定义 Hook 都以
use
开头命名,如useXxxx
。状态 useState、useReducer
useState 和 useReducer 都可以获取/操作状态
使用 useState
const [state, setState] = useState(0)
传入一个参数作为初始值,得到数组<p>{ state }</p>
<button onClick={()=>setState(state=>state+1)}>Change</button>
获取状态,例如:
注意事项 1:不可以局部刷新
如果 state 是一个对象,不能部分 setState,因为 setState 不会帮我们合并属性(后面 useReducer 也不会合并属性)
需要自己手动合并属性
注意事项 2:对象地址需要变化
setState(obj) 如果 obj 地址不变,那么 React 就认为数据没有变化(原来的 obj 变了,但是视图不更新)
useState 接收函数:该函数返回初始 state,且只执行一次
setState 是一个函数(建议优先使用这种,而不是直接写值)
探究 useState 实现
分析:
<App />
<App />
每次调用 App(),都会运行 useState(0)
setN 的时候 n 不会变,App() 会重新执行
App 重新执行,useState(0) 的时候,n 的值每次是不一样的(打印出来)
setN
<App />
重新渲染 re-renderuseState
x
尝试:
当使用多次 useState1 的时候,只有一个
_state
改进思路:把
_state
_state={n:0, m:0}
,因为不能提前知道变量名,所以排除_state=[0,0]
,貌似可以不能在 if 中使用 useState,因为用的数组对顺序要求很严格,第一次渲染和后面的渲染顺序应该一致
总结:
state[index]
使用 useReducer
用来践行 Flux/Redux 的思想
const initialState = { n: 0 }
action.type
执行对应操作/返回对应值const reducer = (state, action) => {}
const [state, dispatch] = useReducer(reducer, initialState)
dispatch({type: '操作类型', other: '其他'})
<p>{state.n}</p>
例如:
就相当于 useState,但是把所有的操作都聚拢在一起了
useContext 上下文
上下文
使用方法:
C=createContext(initial)
创建上下文<C.provider>
圈定作用域useContext(C)
来使用上下文注意事项:
不是响应式的,而是自顶向下逐级通知使用到了就重新渲染的过程(在一个模块将 C 里面的值改变,另一个模块不会感知到这个变化)
使用 useReducer 和 useContext 代替 Redux
步骤:
编程技巧:表驱动
副作用 useEffect
副作用
用途
[]
return
特点:如果同时存在多个 useEffect,会按照出现次序执行
布局副作用 useLayoutEffect
布局副作用
特点
经验:为了用户体验,优先使用 useEffect(优先渲染)
记忆 useMemo 和 useCallback
看代码:
点击按钮的时候,n 改变了,App 执行没问题,但是发现 Child 也执行了
优化技巧:使用 memo 把 Child 再封装一下,这样 Child 就只会在它 props 变化的时候渲染
现在点击按钮的时候 Child2 就不会重新执行了,修改按钮 setM 发现 Child2 重新渲染
Child 只用了一次,所以直接放到 memo 里
但是,如果添加了监听函数之后
现在再点击 App 中的按钮,Child 也会重新执行
App 重新执行,
const onClickChild = () => {}
这句也重新执行,是不同的空函数(新旧函数虽然功能一样,但是地址不一样),导致 props 变了,所以 Child 会重新执行,可以使用 useMemo 解决useMemo 用于缓存新旧组件迭代的时候,使用上次的值
() => value
如果 value 是个函数,就需要写成
useMemo(() => x => console.log(x))
,这是一个返回函数的函数,可以使用 useCallback 解决,它第一个参数可以只写返回的那个函数语法糖,
useCallback(x => log(x), [x])
等价于useMemo(() => x => log(x), [x])
引用 useRef
目的:
const count = useRef(0)
count.current
.current
可以确保两次 useRef 的是同一个值(引用)useRef 不会在变化时自动 render
UI = f(data)
,它给了更多的自由useState 会生成“分身”,useRef 不会自动 render
useState 会自动生成新的 n
通过时间差可以看出生成了新的 n(React 的函数式它使用变量,不倾向于修改它)
那想要一个强制的贯穿始终的状态怎么办
window.xxx
nRef 一直是同一个,使用 setN 进行更新
useRef 贯穿一个组件的前中后
forwardRef
使用 ref 引用到 DOM 对象(类组件可以直接使用)
useRef 可以用来引用 DOM 对象,也可以引用普通对象
由于 props 不包含 ref(大部分时候不需要),所以需要用 forwardRef 包一层
useImperativeHandle
应该叫 setRef
正常打印的是 button 对象,现在返回一个封装了的对象
自定义 Hook
看下这个
一般把 hook 放到
src/hooks
目录下,文件名为useList.js
(hook 以use
开头),暴露一下读写接口升级一下,增加其他接口
尽量使用自定义 Hook,不要直接在组件上方用 useState 等
stale closure 过时闭包
Be Aware of Stale Closures when Using React Hooks (dmitripavlutin.com)
解决:
1、重新获取
const latestLog = c()
(最新的 log)2、把
const message
放到 log 里(最新的 value)useEffect 中
解决:要打印它就把它放到依赖中
[count]
(注意:好习惯需要在 willUnmount 的时候clearInterval(id)
)useState 也是,推荐使用的是 setN 里放函数,
setN(i => i + 1)