React Hook基本总结

State

引入方式

1
import React, {useState, useReducer} from 'react'

useState声明方式

1
const [state, setState] = useState(initialState);

useState返回后的第一个值将始终是更新后的最新的state

setState修改状态方法

1
2
3
4
5
6
7
8
9
10
11
function Counter({initialCount}) {
const [count, setCount] = useState(initialCount);
return (
<>
Count: {count}
<button onClick={() => setCount(initialCount)}>Reset</button>
<button onClick={() => setCount(prevCount => prevCount - 1)}>-</button>
<button onClick={() => setCount(prevCount => prevCount + 1)}>+</button>
</>
);
}

useReducer声明方式

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

e.g.

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>
</>
);
}

Effect

useEffect

1
2
3
4
5
useEffect(() =>{
console.log('effect invoked')//didmount
return () => console.log('effect detected')//willUnmount
}, [count]//only when count changes, rebuild
);

useLayoutEffect

  • 函数签名与useEffect相同
  • 会在任何state更新后,计算新DOM节点数,还未更新到真实DOM页面时执行
  • 执行顺序
1
2
3
4
5
6
Layout effect invoked
b_old.js:65 effect invoked
b_old.js:74 layout effect detected
b_old.js:73 Layout effect invoked
b_old.js:66 effect detected
b_old.js:65 effect invoked

Context

Context is designed to share data that can be considered “global” for a tree of React components, such as the current authenticated user, theme, or preferred language.

React.createContext() && useContext()

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
const themes = {
light: {
foreground: "#000000",
background: "#eeeeee"
},
dark: {
foreground: "#ffffff",
background: "#222222"
}
};

const ThemeContext = React.createContext(themes.light);

function App() {
return (
<ThemeContext.Provider value={themes.dark}>
<Toolbar />
</ThemeContext.Provider>
);
}

function Toolbar(props) {
return (
<div>
<ThemedButton />
</div>
);
}

function ThemedButton() {
const theme = useContext(ThemeContext);
return (
<button style={{ background: theme.background, color: theme.foreground }}>
I am styled by theme context!
</button>
);
}

useRef

  • 获取某一个DOM节点,或组件的实例
  • ref过去在functional component没有,因为无this,无法挂载
  • 返回同一个对象,不会生成新的对象
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
function MyCountFunc() {
// const[count, setCount] = useState(0)//[a, b]

const [count, dispatchCount] = useReducer(countReducer, 0)
const [name, setName] = useState('John')

const inputRef = useRef()
useEffect(()=>{
console.log(inputRef)
return () =>console.log('effect detected')//willUnmount

}, [count])//dependency数组中的项,如果未变化,则不会执行effect,且不会卸载effect

//会在任何state更新后,计算新DOM的节点数,还未更新到真实dom页面,执行
//layout实际使用比较少,如果执行了很多需要长时间运行的代码,会导致渲染时间长,页面卡住
useLayoutEffect(()=>{
console.log('Layout effect invoked')//didmount
return () =>console.log('layout effect detected')//willUnmount
}, [count])//dependency数组中的项,如果未变化,则不会执行effect,且不会卸载effect

// return <span>{count}</span>
return (
<div>
<input ref={inputRef} value={name} onChange={(e)=>setName(e.target.value)}/>
<button onClick={()=>dispatchCount({type: 'add'})}>{count}</button>
<p>{Context}</p>
</div>
)
}

Hooks Render Optimization

  • memo
  • useMemo: 普遍用于对函数中参数的优化
  • useCallback: 普遍用于对函数的优化
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
function MyCountFunc() {
const [count, dispatchCount] = useReducer(countReducer, 0);
const [name, setName] = useState('John')

const countRef = useRef()//返回同一个对象,不会生成新的对象
countRef.current = count

const config = useMemo(()=>({
text: `count is ${count}`,
color: count>3? 'red': 'blue'
})
, [count])

const handleButtonClick = useCallback(() => dispatchCount({type: 'add'}), [dispatchCount])

const handleAlertButtonClick = function() {
setTimeout(()=>{
alert(countRef.current)
}, 2000)
}

return (
<div>
<input value={name} onChange={e=>setName(e.target.value)}/>
<Child
config = {config}
onButtonClick={handleButtonClick}
/>
<button onClick={handleAlertButtonClick}>alert count</button>
</div>
)
}

const Child = memo(
function Child( {onButtonClick, config}) {
console.log('child render');
return (
<button onClick={onButtonClick} style={{color: config.color}}>
{config.text}
</button>
)
}
)

Closure Trap

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
function MyCountFunc() {
const [count, dispatchCount] = useReducer(countReducer, 0);
const [name, setName] = useState('John')

const countRef = useRef()//返回同一个对象,不会生成新的对象
countRef.current = count

const config = useMemo(()=>({
text: `count is ${count}`,
color: count>3? 'red': 'blue'
})
, [count])

const handleButtonClick = useCallback(() => dispatchCount({type: 'add'}), [dispatchCount])

const handleAlertButtonClick = function() {
setTimeout(()=>{
alert(countRef.current)
}, 2000)
}

return (
<div>
<input value={name} onChange={e=>setName(e.target.value)}/>
<Child
config = {config}
onButtonClick={handleButtonClick}
/>
<button onClick={handleAlertButtonClick}>alert count</button>
</div>
)
}

在调用setTimeout函数时,如果将count作为参数,则输出的是调用该函数时的count对象。如果想获取即时的值,则使用useRef