My Little World

hook API学习

1
import React,{useState,useEffect}from 'react';

useState

用于生成state数据和其对应的替换函数

1
2
3
4
5
6
7
const [state,setState] = useState(initialState)
state 即为声明的变量
setState 即更新函数
initialState即state初始值,不传入时为undefined
使用:
const [count,setCount] = useState(0)
const [obj,setObj] = useState({a:1})

useEffect

render执行结束之后执行effect

配置多个effect时从上到下依次执行

如果effect函数返回了一个函数,这个函数将会在组件卸载时执行,负责清除副作用

如果想让effect仅在某些情况下执行,可以传入第二个参数,
第二参数内为effect依赖的值,当值变化时,才会执行,否则跳过不执行

如果想执行只运行一次的 effect(仅在组件挂载和卸载时执行),可以传递一个空数组([])作为第二个参数。这就告诉 React 你的 effect 不依赖于 props 或 state 中的任何值,所以它永远都不需要重复执行。这并不属于特殊情况 —— 它依然遵循依赖数组的工作方式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
function App() {
const [data,setData] = useState(3);
useEffect(()=>{
console.log('useEffect');
let id = setTimeout(()=>{
setData({a:1})
// clearTimeout(id)
},5000)
return ()=>{ //卸载时执行
clearTimeout(id)
}
},[]) //传入第二个参数,控制是否每次render完都执行

useEffect(()=>{
console.log('会从上到下执行')
})
return (
<div>
{
(()=>{
console.log('render')
return null
})()
}
<p>{JSON.stringify(data)}</p>
</div>
)
}

useContext

1
2
const MyContext = React.createContext();
const value = useContext(MyContext);

接收一个 context 对象(React.createContext 的返回值)并返回该 context 的当前值。当前的 context 值由上层组件中距离当前组件最近的 <MyContext.Provider> 的 value prop 决定

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
const themes = {
light: {
foreground: "#000000",
background: "#eeeeee"
},
dark: {
foreground: "#ffffff",
background: "#222222"
}
};
const ThemeContext = React.createContext(); //创建context 对象,可传入value初始值

function App() {
let [thems,setThems] = useState(themes.dark)
useEffect(()=>{
console.log('useEffect');
let id = setTimeout(()=>{
setThems(themes.light)
},5000)
return ()=>{
clearTimeout(id)
}
},[])
return (
<ThemeContext.Provider value={thems}>
<Toolbar />
<Child2 />
</ThemeContext.Provider>
)
}
function Toolbar(props) {
return (
<div>
<ThemedButton />
</div>
);
}

function ThemedButton() {
//获取最近的ThemeContext.Provider 标签上value的绑定值
const theme = useContext(ThemeContext);
return (
<button style={{ background: theme.background, color: theme.foreground }}>
I am styled by theme context!
</button>
);
}
//子组件不依赖constext的数据时,使用React.memo可以避免在context有变化时进行的更新
const Child2 = React.memo((props)=>{
console.log('fire')
return <div>xxxxxx</div>
})

useRef

1
const refContainer = useRef(initialValue);

返回对象ref可以用于绑定dom对象
ref.current属性被初始化为传入的参数,绑到DOM对象后,指向绑定的dom
useRef会在每次渲染时返回同一个ref对象,在整个组件的生命周期内是唯一的

ref.current 可以存储那些不需要引起页面重新渲染的数据
如果刻意地想要从某些异步回调中读取最新的state,可以用一个ref来保存,读取,修改

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function TextInputWithFocusButton() {
const inputEl = useRef(null);//生成绑定对象
const onButtonClick = () => {
// `current` 指向已挂载到 DOM 上的文本输入元素
inputEl.current.focus();
};
return (
<>
<input ref={inputEl} type="text" />
<button onClick={onButtonClick}>Focus the input</button>
</>
);
}
export default App;

当 ref 对象内容发生变化时,useRef 并不会通知你。
变更 .current 属性不会引发组件重新渲染。
如果想要在 React 绑定或解绑 DOM 节点的 ref 时运行某些代码,则需要使用回调 ref 来实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function MeasureExample() {
const [height, setHeight] = useState(0);
console.log('fire')
const measuredRef = useCallback(node => {
if (node !== null) {
setHeight(node.getBoundingClientRect().height);
}
},[]);
return (
<>
<h1 ref={measuredRef}>Hello, world</h1>
<h2>The above header is {Math.round(height)}px tall</h2>
</>
);
}

获取 DOM 节点的位置或是大小的基本方式是使用 callback ref
每当 ref 被附加到一个另一个节点,React 就会调用 callback

当 ref 是一个对象时它并不会把当前 ref 的值的 变化 通知到我们。
使用 callback ref 可以确保 即便子组件延迟显示被测量的节点 (比如为了响应一次点击),我们依然能够在父组件接收到相关的信息,以便更新测量结果

useCallback

1
2
3
4
5
6
const memoizedCallback = useCallback(
() => {
doSomething(a, b);
},
[a, b],
);

把内联回调函数及依赖项数组作为参数传入 useCallback,它将返回该回调函数的 memoized 版本,该回调函数仅在某个依赖项改变时才会更新。
当你把回调函数传递给经过优化的并使用引用相等性去避免非必要渲染(例如 shouldComponentUpdate)的子组件时,它将非常有用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
function Child({event,data}){
console.log('child-render')
useEffect(()=>{
console.log('child-effect')
event()
},[event])
return(
<div>
<p>child</p>
<button onClick = {event}>调用父event</button>
</div>
)
}
const set = new Set()
function Test(){
const [count,setCount] = useState(0)
const [data,setData] = useState({})
const handle = useCallback(async ()=>{
function temp(){
setTimeout(()=>{
return 'xxxx'
},1000)
}
const res = await temp()
setData(res)
console.log('paent-useCallback',data)
},[count]) //count变化才会生成新的handle,才会引起child组件的useEffect的执行
set.add(handle)
console.log('parent-render====>',data)
return (
<div>
<button
onClick={e=>{
setCount(count + 1)
}}
>
count++
</button>
<p>set size:{set.size}</p>
<p>count:{count}</p>
<Child event={handle} />
</div>
)
}

useCallback(fn, deps) 相当于 useMemo(() => fn, deps)。

useMemo

const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);

返回一个 memoized 值
会在render 前执行
把“创建”函数和依赖项数组作为参数传入 useMemo,它仅会在某个依赖项改变时才重新计算 memoized 值。这种优化有助于避免在每次渲染时都进行高开销的计算

如果没有提供依赖项数组,useMemo 在每次渲染时都会计算新的值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
function Test(){
const [count,setCount] = useState(0)
const handle1 = useMemo(()=>{
console.log('handle1',count)
return count
},[])
console.log('render-parent')
return (
<div>
<p>
demo:{count}
<button onClick={()=>setCount(count+1)}>++COUNT</button>
</p>
<Child handle = {handle1} />
</div>
)
}
function Child({handle}){
console.log('render-child')
return (
<div>
<p>child</p>
<p>prop-data:{handle}</p>
</div>
)
}

打印结果:
handle1 0
render-parent
render-child

useReducer

1
const [state, dispatch] = useReducer(reducer, initialArg, init);

useState 的替代方案。
它接收一个形如 (state, action) => newState 的 reducer,
并返回当前的 state 以及与其配套的 dispatch 方法。

适用场景
state 逻辑较复杂且包含多个子值;
下一个 state 依赖于之前的 state
给那些会触发深更新的组件做性能优化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
const initialState = {count: 0};

function reducer(state, action) {
switch (action.type) {
case 'increment':
return {count: state.count + 1};
case 'decrement':
return {count: state.count - 1};
default:
throw new Error();
}
}

function Counter() {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<>
Count: {state.count}
<button onClick={() => dispatch({type: 'decrement'})}>-</button>
<button onClick={() => dispatch({type: 'increment'})}>+</button>
</>
);
}

初始化有两种方式
1.传入第二个参数指定初始值

1
2
3
4
const [state, dispatch] = useReducer(
reducer,
{count: initialCount}
);

2.惰性初始化
将 init 函数作为 useReducer 的第三个参数传入,这样初始 state 将被设置为 init(initialArg)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
function init(initialCount) {
return {count: initialCount};
}

function reducer(state, action) {
switch (action.type) {
case 'increment':
return {count: state.count + 1};
case 'decrement':
return {count: state.count - 1};
case 'reset':
return init(action.payload);
default:
throw new Error();
}
}

function Counter({initialCount}) {
const [state, dispatch] = useReducer(reducer, initialCount, init);
return (
<>
Count: {state.count}
<button
onClick={() => dispatch({type: 'reset', payload: initialCount})}>
Reset
</button>
<button onClick={() => dispatch({type: 'decrement'})}>-</button>
<button onClick={() => dispatch({type: 'increment'})}>+</button>
</>
);
}

以将用于计算 state 的逻辑提取到 reducer 外部,这也为将来对重置 state 的 action 做处理提供了便利

如果 Reducer Hook 的返回值与当前 state 相同,React 将跳过子组件的渲染及副作用的执行

参考资料
参考资料