React组件状态管理

235 阅读1分钟

context

React 内置的 context 可以作为 store;全局管理组件之间的交互状态:

组件: /component/A.js

import React, { useContext } from 'react'
import { Context } from '../../store'

const A = () => {
  const { count } = useContext(Context)

  return (
    <div className="a-component">
      <p>A COMPONENT</p>
      <p>{`count: ${count}`}</p>
    </div>
  )
}

export default A

组件: /component/B.js

import React, { useContext } from 'react'
import { Button } from 'antd-mobile'
import { Context } from '../../store'

const B = () => {
  const { count, setCount, onChangeCount } = useContext(Context)

  const handleAdd = () => {
    setCount(count + 1)
  }

  const handleSub = () => {
    setCount(count - 1)
  }

  const handleChange = () => {
    onChangeCount()
  }

  return (
    <div className="b-component">
      <p>B COMPONENT</p>
      <Button type="primary" size="small" onClick={handleAdd}>COUNT + 1</Button>
      <Button type="ghost" size="small" onClick={handleSub}>COUNT - 1</Button>
      <Button type="warning" size="small" onClick={handleChange}>COUNT CHANGE</Button>
    </div>
  )
}

export default B

状态管理:/store.js

import React, { createContext, useState } from 'react'
import { Toast } from 'antd-mobile'

const Context = createContext({})

const Provider = ({ children }) => {
  const [count, setCount] = useState(0)

  // 添加方法,模拟请求
  const onChangeCount = () => {
    // 模拟请求,两秒后修改值
    Toast.loading('等待请求成功...', 20)
    setTimeout(() => {
      Toast.success('请求成功', 2)
      setCount(9999)
    }, 2000)
  }

  const VALUE = {
    count,
    setCount,
    onChangeCount,
  }

  return (
    <Context.Provider value={VALUE}>
      {children}
    </Context.Provider>
  )
}

export {
  Context,
  Provider,
}

入口文件:/index.js

import React from 'react'
import { render } from 'react-dom'

import { Provider } from './store'
import A from './component/A'
import B from './component/B'

const Index = () => (
  <div className="index-wrap">
    <A />
    <B />
  </div>
)

render(
  <Provider>
    <Index />
  </Provider>,
  document.getElementById('root'),
)

但是 context 做为 store 有一个问题,任何组件都能从 context 中取出数据来修改;

那么当排查问题的时候就特别困难,因为并不知道是哪个组件把数据改坏的,也就是数据流不清晰。

因此,此方式适用于业务不是很复杂的项目管理通用的状态。

useContext + useReducer

useContext + useReducer 完全可以替代 React 进行状态管理:

组件: /component/A.js

import React from 'react'
import { useStore } from '../../store'

const A = () => {
  const {
    store: { count },
  } = useStore()

  return (
    <div className="a-component">
      <p>A COMPONENT</p>
      <p>{`count: ${count}`}</p>
    </div>
  )
}

export default A

组件: /component/B.js

import React from 'react'
import { Button, Toast } from 'antd-mobile'
import { useStore } from '../../store'

const B = () => {
  const { dispatch } = useStore()

  const handleAdd = () => {
    dispatch({ type: 'add' })
  }

  const handleSub = () => {
    dispatch({ type: 'sub' })
  }

  // 模拟请求,两秒后修改值
  const handleChange = () => {
    Toast.loading('等待请求成功...', 20)
    setTimeout(() => {
      Toast.success('请求成功', 2)
      dispatch({ type: 'change', data: Math.ceil(Math.random() * 1000) })
    }, 2000)
  }

  return (
    <div className="b-component">
      <p>B COMPONENT</p>
      <Button type="primary" size="small" onClick={handleAdd}>COUNT + 1</Button>
      <Button type="ghost" size="small" onClick={handleSub}>COUNT - 1</Button>
      <Button type="warning" size="small" onClick={handleChange}>COUNT CHANGE</Button>
    </div>
  )
}

export default B

状态管理:/store.js

import React, { createContext, useContext, useReducer } from 'react'

const Context = createContext({})

const pageData = {
  count: 0,
}

const reducer = (state, action) => {
  const { type, data } = action
  const { count } = state

  if (type === 'add') {
    return Object.assign({}, state, { count: count + 1 })
  }

  if (type === 'sub') {
    return Object.assign({}, state, { count: count - 1 })
  }

  if (type === 'change') {
    return Object.assign({}, state, { count: data })
  }
  
  return state
}

const Provider = ({ children }) => {
  const [store, dispatch] = useReducer(reducer, pageData)

  return (
    <Context.Provider
      value={{ store, dispatch }}
    >
      {children}
    </Context.Provider>
  )
}

const useStore = () => {
  const { store, dispatch } = useContext(Context)
  return { store, dispatch }
}

export {
  Provider,
  useStore,
}

入口文件:/index.js

import React from 'react'
import { render } from 'react-dom'

import { Provider } from './store'
import A from './component/A'
import B from './component/B'

const Index = () => (
  <div className="index-wrap">
    <A />
    <B />
  </div>
)

render(
  <Provider>
    <Index />
  </Provider>,
  document.getElementById('root'),
)

useContext 创建全局状态,不用一层一层的传递状态。

useReducer 创建 reducer 根据不同的 dispatch 更新 state。

代码写到哪里状态就加到哪里,不用打断思路跳到 redux 里面去写。

全局状态分离,避免项目变大导致 Redux 状态树难以管理。

文件目录

├── index.js   // 入口文件   
├── store.js   // 状态管理
├── component
    ├── A.js   // 组件A
    └── B.js   // 组件B