美文网首页
使用useContext和useReducer实现简单的状态管理

使用useContext和useReducer实现简单的状态管理

作者: 学不动了Zzz | 来源:发表于2020-08-10 16:44 被阅读0次
基础知识

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)
问题

调用了 useContext 的组件总会在 context 值变化时重新渲染。

image.png

点击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
  }
}

相关文章

网友评论

      本文标题:使用useContext和useReducer实现简单的状态管理

      本文链接:https://www.haomeiwen.com/subject/avcfdktx.html