React生命周期

React16.8+生命周期分为三个阶段,分别为挂载、更新和卸载

挂载

  • constructor: 构造函数,最先被执行,通常在构造函数中初始化state对象或者给自定义方法绑定this
  • static getDerivedStateFromProps(nextProps, prevState)。静态方法,当接收到新的属性想去修改state时,可以使用。
  • render()
  • componentDidMount():组件装载之后调用,此时可以获取到DOM节点并操作,比如对canvas,svg的操作,服务器请求,订阅都可以写在这里面,在componentWillUnmount中取消订阅。

更新阶段

  • static getDerivedStateFromProps() 此方法在更新整个挂在阶段都可能会调用。
  • shouldComponentUpdate(nextProps, nextState) 有两个参数,表示新的属性和变化之后的state,返回一个Boolean, true表示会触发重新渲染,false表示不会触发重新渲染,默认返回true,常利用此生命周期来优化React程序性能。
  • render()
  • getSnapshotBeforeUpdate(prevProps, prevState) 函数的返回值,作为第三个参数传给componentDidUpdate, 如果不想要返回值,可以返回null,此生命周期必须与componentDidUpdate搭配使用
  • componentDidUpdate(prevProps, prevState, snapshot) 该方法该方法在getSnapshotBeforeUpdate方法之后被调用,有三个参数prevProps,prevState,snapshot,表示之前的props,之前的state,和snapshot。第三个参数是getSnapshotBeforeUpdate返回的,如果触发某些回调函数时需要用到 DOM 元素的状态,则将对比或计算的过程迁移至 getSnapshotBeforeUpdate,然后在 componentDidUpdate 中统一触发回调或更新状态。

卸载

  • componentWillUnmount() 当我们的组件被卸载或者销毁了就会调用,我们可以在这个函数里去清除一些定时器,取消网络请求,清理无效的DOM元素等垃圾清理工作

lifecycle

Redux

Work flow

Redux Basis

React Redux Basis

Work flow of redux

redux work flow

Redux Demo

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
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
import {createStore, combineReducers, applyMiddleware} from 'redux'
import ReduxThunk from 'redux-thunk'
import { composeWithDevTools} from 'redux-devtools-extension'

const initialState = {
count: 0
}

const userInitialState = {
username: 'john'
}



const ADD = 'ADD'
function counterReducer(state = initialState, action) {
console.log(state, action)
switch (action.type) {
case ADD:
return {count: state.count + (action.num || 1) }
default:
return state
}
}
const UPDATE_NAME = 'UPDATE_NAME'
function userReducer(state= userInitialState, action) {
switch(action.type) {
case UPDATE_NAME:
return {...state, username: action.name}
default:
return state
}
}

const allReducers = combineReducers({
counter: counterReducer,
user: userReducer
})
// createStore(reducer, [preloadedState], [enhancer])
const store = createStore(allReducers,
{
counter: initialState,
user: userInitialState
},
composeWithDevTools(applyMiddleware(ReduxThunk)) //执行异步dispatch,异步action, 和一个有效的redux调试工具
);

// action create
function add(num) {
return {
type: ADD,
num,
}
}

//
function addAsync(num) {
return (dispatch, getState) => setTimeout(()=>{
dispatch(add(num))
}, 1000)
}

console.log(store.getState())
store.dispatch({type: ADD})
console.log(store.getState())

store.subscribe(()=>{
console.log('changed', store.getState())
})

store.dispatch(add(3))
// store.dispatch({type: ADD})
store.dispatch(addAsync(5))
store.dispatch({type: UPDATE_NAME, name: 'Yehya'})

export default store

React Redux

in _app.js

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
import App, {Container} from 'next/app'
import { Provider } from 'react-redux'

import 'antd/dist/antd.css'
import Layout from "../components/Layout";
import '../styles.css'
import MyContext from '../lib/my-context'
import store from '../store/store'

class MyApp extends App {
state = {
context: 'value'
}
static async getInitialProps({Component, ctx}) {
console.log('init app')
let pageProps = {}
if(Component.getInitialProps) {
pageProps = await Component.getInitialProps(ctx)

}
return {
pageProps
}

}

render() {
const {Component, pageProps} = this.props//每个页面渲染的时候都会作为Component
return (
<Container>
<Layout>
<Provider store={store}>
<MyContext.Provider value={this.state.context}>
<Component {...pageProps}/>
<button onClick={()=>this.setState({context:`${this.state.context}111`})}>update context</button>
</MyContext.Provider>
</Provider>
</Layout>
</Container>
)
}
}

export default MyApp

we import store.

In index.js

1
2
3
4
5
6
7
8
9
10
11
export default connect(function mapStateToProps(state) {
return {
counter: state.counter.count,//exposed by createStore
username: state.user.username
}
}, function mapDispatchToProps(dispatch) {
return {
add: (num)=> dispatch({ type: 'ADD', num}),
rename: (name) => dispatch({type: 'UPDATE_NAME', name})
}
})(Index)

store创建,connect 用法, redux实现flux数据流向 通过点击button 触发 action,action调用reducer,更新state,传递给provider,进入react

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