react-redux为我们做了什么?
在没有react-redux的情况下使用redux,你需要在每一个需要使用redux的组件中引入store,如果需要修改store中的数据还需要引入action,在某一个事件的回调函数中去dispatch,如下(经典计数器案例)
import React, { PureComponent } from 'react'
import store from '../store'
import { increamentAction } from '../store/counter/actionCreators'
export default class Home extends PureComponent {
constructor(props) {
super(props)
this.state = {
// 第一次获取counter值
counter: store.getState().counter.counter
}
}
componentDidMount() {
// 订阅store,发生变化以后重新设置counter值
store.subscribe(() => {
this.setState({ counter: store.getState().counter.counter })
})
}
increament(adder) {
// 在click的回调事件中dispatch increament的action
store.dispatch(increamentAction(adder))
}
render() {
const { counter } = this.state
return (
<div>
<h2>Home Counter: {counter}</h2>
<button onClick={() => this.increament(1)}>+1</button>
</div>
)
}
}
两个问题
- 每一个组件都需要引入store,并且要订阅数据,首次渲染还需要手动获取,十分麻烦
- 每一个action都需要包裹在一个回调中
react-redux将store和组件之间建立起了联系
具体使用不说了,下面是上面案例使用react-redux的重构版本
import React, { PureComponent } from 'react'
import { connect } from 'react-redux'
import { increamentAction } from '../store/counter/actionCreators'
class Home extends PureComponent {
render() {
const { counter, increament } = this.props
return (
<div>
<h2>Home Counter: {counter}</h2>
<button onClick={() => increament(1)}>+1</button>
</div>
)
}
}
const mapStateToProps = state => ({
counter: state.counter.counter
})
const mapDispatchToProps = dispatch => ({
increament: adder => dispatch(increamentAction)
})
export default connect(mapStateToProps, mapDispatchToProps)(Home)
当然了,这里的counter不是平白无故来的,还需要给App组件包裹一个Provider组件,具体可以看看官方文档
分析原理
Provider
你可以发现react-redux为每一个组件提供的store出现在了props里面,这明显是使用了Context,Context 提供了一个无需为每层组件手动添加 props,就能在组件树间进行数据传递的方法
connect
connect明显是一个函数,而且是一个高阶函数,因为它调用的结果又再次调用了。并且再次调用传入的参数是组件,如果要渲染传入的组件,那么返回值也需要是一个组件,所以connect返回的函数是一个高阶组件。如果是高阶组件的话,我们就可以对传入的组件进行进一步的传值,高阶组件服用逻辑还是很方便的。
基本实现
connect
src
├── App.jsx
├── index.js
├── my-react-redux
│ └── connect.js +
├── components
│ └── Home.jsx
└── store
├── counter
│ ├── actionCreators.js
│ ├── constants.js
│ └── reducer.js
└── index.js
connect.js
我们之所以能够从props中取到数据,正是高阶组件的功劳
import { useEffect, useState } from "react";
import store from "../store";
export default function(mapStateToProps, mapDispatchToProps) {
// 返回一个高阶组件
return function(WrapperComponent) {
// 返回一个组件
return function(props) {
// 处理state以及actions传入props
const [state, setState] = useState(mapStateToProps(store.getState()))
const dispatch = mapDispatchToProps(store.dispatch)
useEffect(() => {
const unsubscribe = store.subscribe(() => setState(store.getState()))
return () => unsubscribe()
})
return (
<WrapperComponent {...props} {...state} {...dispatch}/>
)
}
}
}
但是现在这个connect函数还是太依赖store了,所以Provider的作用就来了
Provider
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
import { Provider } from 'react-redux'
import store from './store';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<Provider store={store}>
<App/>
</Provider>
);
Context是通过一个Context.Provider来包裹组件,并且传值属性名是value,所以这里Provider组件是做了进一步的封装
src
├── App.jsx
├── index.js
├── my-react-redux
│ ├── Provider.jsx +
│ ├── storeContext.js +
│ └── connect.js
├── components
│ ├── Home.jsx
└── store
├── counter
│ ├── actionCreators.js
│ ├── constants.js
│ └── reducer.js
└── index.js
storeContext.js
import { createContext } from 'react'
export const storeContext = createContext()
Provider.jsx
import React from "react";
import storeContext from "./storeContext";
export default (props) => (
<storeContext.Provider value={props.store}>
{props.children}
</storeContext.Provider>
);
Provider就这么简单
本文代码已经放在CodeSandBox啦!