React-redux的使用和实现
前一篇文章我们讲述了redux的实现,通过上一篇文章了解了中间件实现的原理和实现,但是redux本身对于状态的管理的使用上还是有一些不方便的地方
- 对于状态
state,需要通过额外的store.getState(),不能直接通过组件的props属性接受 - 对于组件的使用需要的
state属性和不需要的属性没有一个分离 - 对于
state发生了改变的时候,不能自动更新试图,需要通过store.subscribe()来订阅数据
基于上面的redux的问题,react-redux通过使用高阶组件和Context优化了redux的一些问题
React-redux的基本使用
react-redux主要是有通过2个api
Provider为后代组件提供storeconnent为组件提供数据和数据变更的方法
Provider
- 一般将根组件嵌套放在
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
connent是将react组件和redux的store链接起来,然后返回一个新的已经和redux store 连接起来的组件类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
connent()的第一个参数,可以省略或者传递null,不订阅state更新- 一般只返回组件所需要属性,当属性的值发生改变时,会自动渲染组件,并传递新的值
- 不要直接按照下面的方式写,因为这样只要
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)
- 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,让我们更加清楚的了解它的原理
Provider的实现connent的实现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文件
- 我们需要创建一个
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中的state和dispatch, 映射到组件的props上,实现connect主要是注意一下几点
connent()是一个返回的是一个高阶组件,接受一个组件,返回一个新的组件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的实现
- 当
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
useStore: 获取store实例useSelector: 获取store的一些state状态,类似于mapStateToProps,但是又有一些不同的地方,具体不同可以查看官网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
}