基础知识
context useContext useReducer useMemo memo useCallback
例子
// 我们可以在子组件中使用useContext访问改变父组件的状态
import React, { createContext, useContext, useReducer } from 'react'
const context = createContext({})
const A = () => {
const { state } = useContext(context)
console.log('render a')
return (
<div>
A:{state.a}
<B />
</div>
)
}
const B = () => {
const { state, dispatch } = useContext(context)
console.log('render b')
return (
<div>
B:{state.b}
<button onClick={() => {dispatch({type: 'addA'})}}>adda</button>
<button onClick={() => {dispatch({type: 'addB'})}}>addb</button>
</div>
)
}
export default () => {
const [state, dispatch] = useReducer((state, action) => {
switch (action.type) {
case 'addA':
return {...state, a: state.a + 1}
case 'addB':
return {...state, b: state.b + 1}
default:
throw new Error()
}
}, {a: 0, b: 0})
console.log('render p')
return (
<context.Provider value={{state, dispatch}}>
<div>
Parent
<A />
</div>
</context.Provider>
)
}
封装自己的状态管理(Provider & connect)
模仿dva的写法(异步可以在effects里处理)
// context.js
import React, { createContext, useReducer, useContext } from 'react'
export default ({ namespace, state, reducers = {}, effects = {} }) => {
const Context = createContext({})
const Provider = ComponentUi => {
const Components = props => {
const [providerState, dispatch] = useReducer((state, { type, ...args }) => {
if (reducers[type]) {
return reducers[type](state, args)
} else {
return state
}
}, state)
const _dispatch = ({type, ...args}) => {
if (effects[type]) {
effects[type].call(null, args, dispatch)
} else if (reducers[type]) {
dispatch({type, ...args})
}
}
return (
<Context.Provider value={{[namespace]: providerState, dispatch: _dispatch}}>
<ComponentUi { ...props } />
</Context.Provider>
)
}
return Components
}
const connect = fn => ComponentUi => {
const Component = props => {
const context = useContext(Context)
const selectState = typeof fn === 'function' ? fn(context) : context
const newProps = { ...props, ...selectState }
return <ComponentUi {...newProps} />
}
return Component
}
return {
Context,
Provider,
connect
}
}
// model.js
import getContext from './context'
// import { FetchMenus } from '@/apis'
export const { Provider, connect } = getContext({
namespace: 'common',
state: {
a: 0,
d: 0
},
effects: { // 副作用 可以处理异步
async fetchMenus (_, dispatch) {
// const { data = [] } = await FetchMenus()
// dispatch({
// type: 'put',
// menus: data
// })
}
},
reducers: {
put (state, data) {
return { ...state, ...data }
},
addA (state) {
return { ...state, a: state.a + 1 }
}
}
})
// parent.jsx
import React from 'react'
import A from './a.jsx'
import B from './b.jsx'
import C from './c.jsx'
import { Provider } from './models'
import '../App.css'
function App() {
console.log('render: Parent')
return (
<div className="App">
<h1>Parent</h1>
<A />
<B />
<C />
</div>
)
}
export default Provider(App)
// a.jsx
import React from 'react'
import { connect } from './models'
const A = ({common}) => {
console.log('render: A')
return (
<div>A: {common.a}</div>
)
}
export default connect()(A)
// b.jsx
import React from 'react'
import { connect } from './models'
const B = props => {
const {
dispatch
} = props
return (
<div>
{console.log('render: B')}
B
<button onClick={() => {dispatch({type: 'addA'})}}>A++</button>
<button onClick={() => {dispatch({type: 'put', d: 10})}}>D++</button>
</div>
)
}
export default connect(({dispatch}) => ({dispatch}))(B)
// c.jsx
import React from 'react'
import D from './d'
export default function C () {
console.log('render: C')
return (
<div>
<p style={{padding: '20px'}}>C</p>
<D />
</div>
)
}
// d.jsx
import React from 'react'
import { connect } from './models'
const D = (props) => {
const { d } = props
console.log('render: D')
return (
<div>
D: {d}
</div>
)
}
export default connect(({common: { d }}) => ({ d }))(D)
问题
image.png调用了 useContext 的组件总会在 context 值变化时重新渲染。
点击A++按钮 会造成所有组件的重新渲染,这显然不是我们期望的。这里就需要使用memo,useMemo,useCallback来进行优化。
优化
1.拆分context 将频繁更新的context拆出来,避免不必要的render
2.避免不必要的dispatch,在dispatch之前做数据比较
3.memo默认会对props做浅比较,在给定相同 props 的情况下渲染相同的结果
// d.jsx
import React, { memo } from 'react'
import { connect } from '../models/common'
const D = (props) => {
const { d } = props
console.log('render: D')
return (
<div>
D: {d}
</div>
)
}
export default connect(({common: { d }}) => ({ d }))(memo(D))
4.useCallback会返回一个 memoized 回调函数,依赖不变时(浅比较),不会返回新函数
// context.js
const _dispatch = useCallback(({type, ...args}) => {
if (effects[type]) {
effects[type].call(null, args, dispatch)
} else if (reducers[type]) {
dispatch({type, ...args})
}
}, [])
// b.jsx
import React, { memo } from 'react'
import { connect} from '../models/common'
const B = props => {
const {
dispatch
} = props
return (
<div>
{console.log('render: B')}
B
<button onClick={() => {dispatch({type: 'addA'})}}>A++</button>
<button onClick={() => {dispatch({type: 'put', d: 10})}}>D++</button>
</div>
)
}
export default connect(({dispatch}) => ({dispatch}))(memo(B))
5.useMemo会返回一个 memoized 值,依赖不变时(浅比较),不会重新计算
下面的组件虽然会重新执行,但是不会再re-render子树
// a.jsx
import React, { useMemo } from 'react'
import { connect } from '../models/common'
const A = ({common}) => {
return useMemo(() => {
console.log('render: A')
return (
<div>A: {common.a}</div>
)
}, [common.a])
}
export default connect()(A)
完整的context代码
// context.js
import React, { createContext, useReducer, useContext, useMemo, useCallback } from 'react'
export default ({ namespace, state, reducers = {}, effects = {} }) => {
const Context = createContext({})
const Provider = ComponentUi => {
const Components = props => {
const [providerState, dispatch] = useReducer((state, { type, ...args }) => {
if (reducers[type]) {
return reducers[type](state, args)
} else {
return state
}
}, state)
const _dispatch = useCallback(({type, ...args}) => {
if (effects[type]) {
effects[type].call(null, args, dispatch)
} else if (reducers[type]) {
dispatch({type, ...args})
}
}, [])
return (
<Context.Provider value={{[namespace]: providerState, dispatch: _dispatch}}>
{ useMemo(() => <ComponentUi { ...props } />, [props]) }
</Context.Provider>
)
}
return Components
}
const connect = fn => ComponentUi => {
const Component = props => {
const context = useContext(Context)
const selectState = typeof fn === 'function' ? fn(context) : context
const newProps = { ...props, ...selectState }
return <ComponentUi {...newProps} />
}
return Component
}
return {
Context,
Provider,
connect
}
}
网友评论