React-redux的使用和实现

557 阅读5分钟

React-redux的使用和实现

前一篇文章我们讲述了redux的实现,通过上一篇文章了解了中间件实现的原理和实现,但是redux本身对于状态的管理的使用上还是有一些不方便的地方

  1. 对于状态state,需要通过额外的store.getState(),不能直接通过组件的props属性接受
  2. 对于组件的使用需要的state属性和不需要的属性没有一个分离
  3. 对于state发生了改变的时候,不能自动更新试图,需要通过store.subscribe()来订阅数据

基于上面的redux的问题,react-redux通过使用高阶组件和Context优化了redux的一些问题

React-redux的基本使用

react-redux主要是有通过2个api

  1. Provider 为后代组件提供store
  2. connent 为组件提供数据和数据变更的方法

Provider

  1. 一般将根组件嵌套放在Provider里面,然后子组件可以通过connent来访问state和修改状态,
import App from './App'
import { Provider } from 'react-redux'
import store from './store'

ReactDOM.render(
    <Provider store={store}>
        <App />
    </Provider>,
    document.getElementById('root')
)

connent

  1. connent是将react组件和redux的store链接起来,然后返回一个新的已经和redux store 连接起来的组件类
  2. connent()接受2个参数,都是可以省略的。第一个参数是将store中的state映射到props中,第二个是将一些actions映射到组件的props中 比如说,我们的App组件只需要state中的age属性和提交一个action去修改age属性
import './App.css'
import { connect } from 'react-redux'

function App(props) {
  return (
      <h1>app</h1>
  )
}
export default connect()(App)
mapStateToProps 映射state到组件的props
  1. connent()的第一个参数,可以省略或者传递null,不订阅state更新
  2. 一般只返回组件所需要属性,当属性的值发生改变时,会自动渲染组件,并传递新的值
  3. 不要直接按照下面的方式写,因为这样只要state发生改变就会渲染组件,造成性能丢失
import { connect } from 'react-redux'
connect(state => state)(App)

mapDispatchToProps 映射dispatch到组件上

1.connent()的第一个参数,可以省略或者传递null, 就会获取到默认将dispatch传递给组件的props, 组件内部可以通过dispatch提交action更新状态

function App(props) {
  console.log(props) // {age: 27, dispatch: f}
  return (
      <div>App</div>
  )
}
export default connect((state) => {
  return {
    age: state.age
  }
}, null)(App)
  1. mapDispatchToProps可以是一个函数或者一个对象,如果是一个函数的话,会接受dispatch作为第一个参数, 如果是一个对象的话,每一个字段的值都必须要是一个 action creator
export default connect((state) => {
  return {
    age: state.age
  }
}, (dispatch) => {
  return {
    add: () => dispatch({type: 'ADD'}),
    minus: () => dispatch({type: 'MINUS'}),
  }
})(App)

在上面的一个例子中,我们使用的是一个函数的形式,接受dispatch作为第一个参数,然后返回一个对象,你会发现,我们每一个action都使用了dispatch的对象,redux提供了一个方法 bindActionCreators

export default connect((state) => {
  return {
    age: state.age
  }
}, (dispatch) => {
  const actions = {
    add: () => ({type: 'ADD'}),
    minus: () => ({type: 'MINUS'}),
  }
  return bindActionCreators(actions, dispatch)
})(App)

bindActionCreators 接受一个对象,这个对象的值是一个可以返回action的函数, 如果mapDispatchToProps是一个对象的话,内部实现还是会将其转换成函数,简化了一些写法,所以react-redux也是推荐我们使用对象的形式, 下面的一种方式就是和上面我们使用函数是一样的,内部底层也是这样简化对象的。

export default connect((state) => {
  return {
    age: state.age
  }
},{
  add: () => ({ type: 'ADD' }),
  minus: () => ({ type: 'MINUS' }),
})(App)

自己实现React-redux

通过上面的介绍和使用例子,我们应该清楚的了解了react-redux的使用,接下来我们将手动实现主要api,让我们更加清楚的了解它的原理

  1. Provider的实现
  2. connent的实现
  3. bindActionCreators的实现

Provider()的实现

首先在使用Provider的时候,我们是下面这样使用的,将顶层组件作为子组件,通过传递store属性,所有的组件都可以共享这个值

import { Provider } from 'react-redux'
ReactDOM.render(
    <Provider store={store}>
      <App type="name" />
    </Provider>,
    document.getElementById('root')
)

组件数据自上而下的传递,让我们想到了react的Context api, 可以传递数据。下面我们创建一个hcc-react-redux.js文件

  1. 我们需要创建一个Context,通过Context传递内容
    export const Context = React.createContext({})
    export const Provider = function (props) {
      // 获取到redux的store实例
      const { store, children } = props
      return (
          <Context.Provider value={store}>
            {children}
          </Context.Provider>
      )
    }
    

connect()的实现

react-redux 中比较重要的就是这个api了, 它将我们的store中的statedispatch, 映射到组件的props上,实现connect主要是注意一下几点

  1. connent()是一个返回的是一个高阶组件,接受一个组件,返回一个新的组件
  2. connent()组件需要订阅更新,当数据更新时,需要更新组件
export const connect = (mapStateToProps = state => state, mapDispatch) => WrapComponent => (props) => {
  const store = useContext(Context)
  const { getState, dispatch, subscribe } = store
  let state = mapStateToProps(getState())
  let dispatchProps = { dispatch }
  const [ignored, forceUpdate] = useReducer(x => x + 1, 0)
  if (typeof mapDispatch === 'function') {
    dispatchProps = mapDispatch(dispatch)
  } else if (typeof mapDispatch === 'object') {
    // 如果是一个对象
    dispatchProps = bindActionCreators(mapDispatch, dispatch)
  }
  useEffect(() => {
    let unsubscribable = subscribe(() => {
      forceUpdate()
    })
    return () => {
      unsubscribable && unsubscribable()
    }
  }, [store, subscribe])
  return <WrapComponent {...props} {...state} {...dispatchProps}/>
}
bindActionCreators的实现
  1. mapDispatchToProps 是一个对象的时候,react-redux 会默认的转换成函数的形式,然后进行执行返回
function bindActionCreators(mapDispatch, dispatch) {
  let result = {}
  Object.keys(mapDispatch).forEach(key => {
    console.log(key)
    result[key] = bindActionCreator(mapDispatch[key], dispatch)
  })
  return result
}

function bindActionCreator(func, dispatch) {
  return (...args) => dispatch(func(...args))
}

React-redux hook的实现

react-redux提供了我们使用函数组件进行操作store

  1. useStore: 获取store实例
  2. useSelector: 获取store的一些state状态,类似于mapStateToProps,但是又有一些不同的地方,具体不同可以查看官网
  3. useDispatch: 让函数组件可以使用dispatch派发动作,更新state的状态
export const useStore = function () {
  return useContext(Context)
}

export const useSelector = function (selector) {
  const store = useStore()
  const { getState, subscribe } = store

  let state = selector(getState())
  const [, forceUpdate] = useReducer(x => x + 1, 0)
  useLayoutEffect(() => {
    console.log('钩子更新')
    const unsubscribable = subscribe(() => {
      forceUpdate()
    })
    return () => {
      if (unsubscribable) {
        unsubscribable()
      }
    }
  }, [subscribe])
  return state
}

export const useDispatch = () => {
  const store = useStore()
  return store.dispatch
}