react-redux 实现原理
摘自:https://juejin.im/post/5def4831e51d45584b585000?utm_source=gold_browser_extension
redux 简单实现,一个简单的订阅发布机制。
// reducer.js
const initialState = {
count: 0
}
export function reducer(state = initialState, action) {
switch (action.type) {
case 'plus':
return {
...state,
count: state.count + 1
}
case 'subtract':
return {
...state,
count: state.count - 1
}
default:
return initialState
}
}
// createStore.js
import { reducer } from './reducer'
export const createStore = (reducer) => {
let currentState = {}
let observers = [] //观察者队列
function getState() {
return currentState
}
function dispatch(action) {
currentState = reducer(currentState, action)
observers.forEach(fn => fn())
}
function subscribe(fn) {
observers.push(fn)
}
dispatch({ type: '@@REDUX_INIT' }) //初始化store数据
return { getState, subscribe, dispatch }
}
const store = createStore(reducer) //创建store
store.subscribe(() => { console.log('组件1收到store的通知') })
store.subscribe(() => { console.log('组件2收到store的通知') })
store.dispatch({ type: 'plus' }) //执行dispatch,触发store的通知
react-redux
若用 redux,一个组件如果想从store存取公用状态,需要进行四步操作:
- import 引入 store
- getState 获取状态
- dispatch 修改状态
- subscribe 订阅更新
代码相对冗余,我们想要合并一些重复的操作,而 react-redux 就提供了一种合并操作的方案:react-redux 提供 Provider 和 connect 两个 API
- Provider 将 store 放进 this.context 里,省去了 import 这一步
- connect 将 getState、dispatch 合并进了this.props,并自动订阅更新,简化了另外三步。
Provider
import React from 'react'
import PropTypes from 'prop-types'
export class Provider extends React.Component {
// 需要声明静态属性childContextTypes来指定context对象的属性,是context的固定写法
static childContextTypes = {
store: PropTypes.object
}
// 实现getChildContext方法,返回context对象,也是固定写法
getChildContext() {
return { store: this.store }
}
constructor(props, context) {
super(props, context)
this.store = props.store
}
// 渲染被Provider包裹的组件
render() {
return this.props.children
}
}
connect
connect的调用方式:connect(mapStateToProps, mapDispatchToProps)(App)
。
export function connect(mapStateToProps, mapDispatchToProps) {
return function (Component) {
class Connect extends React.Component {
componentDidMount() {
//从context获取store并订阅更新
this.context.store.subscribe(this.handleStoreChange.bind(this));
}
handleStoreChange() {
// 触发更新
// 触发的方法有多种,这里为了简洁起见,直接forceUpdate强制更新,读者也可以通过setState来触发子组件更新
this.forceUpdate()
}
render() {
return (
<Component
// 传入该组件的props,需要由connect这个高阶组件原样传回原组件
{...this.props}
// 根据mapStateToProps把state挂到this.props上
{...mapStateToProps(this.context.store.getState())}
// 根据mapDispatchToProps把dispatch(action)挂到this.props上
{...mapDispatchToProps(this.context.store.dispatch)}
/>
)
}
}
//接收context的固定写法
Connect.contextTypes = {
store: PropTypes.object
}
return Connect
}
}
这种实现其实更便于实现装饰器模式。
完整例子
写一个计数器,点击按钮就派发一个 dispatch,让 store 中的 count 加一,页面上显示这个 count
// store.js
export const createStore = (reducer) => {
let currentState = {}
let observers = [] //观察者队列
function getState() {
return currentState
}
function dispatch(action) {
currentState = reducer(currentState, action)
observers.forEach(fn => fn())
}
function subscribe(fn) {
observers.push(fn)
}
dispatch({ type: '@@REDUX_INIT' }) //初始化store数据
return { getState, subscribe, dispatch }
}
//reducer.js
const initialState = {
count: 0
}
export function reducer(state = initialState, action) {
switch (action.type) {
case 'plus':
return {
...state,
count: state.count + 1
}
case 'subtract':
return {
...state,
count: state.count - 1
}
default:
return initialState
}
}
//react-redux.js
//react-redux.js
import React from 'react'
import PropTypes from 'prop-types'
export class Provider extends React.Component {
// 需要声明静态属性childContextTypes来指定context对象的属性,是context的固定写法
static childContextTypes = {
store: PropTypes.object
}
// 实现getChildContext方法,返回context对象,也是固定写法
getChildContext() {
return { store: this.store }
}
constructor(props, context) {
super(props, context)
this.store = props.store
}
// 渲染被Provider包裹的组件
render() {
return this.props.children
}
}
export function connect(mapStateToProps, mapDispatchToProps) {
return function (Component) {
class Connect extends React.Component {
componentDidMount() { //从context获取store并订阅更新
this.context.store.subscribe(this.handleStoreChange.bind(this));
}
handleStoreChange() {
// 触发更新
// 触发的方法有多种,这里为了简洁起见,直接forceUpdate强制更新,读者也可以通过setState来触发子组件更新
this.forceUpdate()
}
render() {
return (
<Component
// 传入该组件的props,需要由connect这个高阶组件原样传回原组件
{...this.props}
// 根据mapStateToProps把state挂到this.props上
{...mapStateToProps(this.context.store.getState())}
// 根据mapDispatchToProps把dispatch(action)挂到this.props上
{...mapDispatchToProps(this.context.store.dispatch)}
/>
)
}
}
//接收context的固定写法
Connect.contextTypes = {
store: PropTypes.object
}
return Connect
}
}
//index.js
import React from 'react'
import ReactDOM from 'react-dom'
import App from './App'
import { Provider } from './react-redux'
import { createStore } from './store'
import { reducer } from './reducer'
ReactDOM.render(
<Provider store={createStore(reducer)}>
<App />
</Provider>,
document.getElementById('root')
);
//App.js
import React from 'react'
import { connect } from './react-redux'
const addCountAction = {
type: 'plus'
}
const mapStateToProps = state => {
return {
count: state.count
}
}
const mapDispatchToProps = dispatch => {
return {
addCount: () => {
dispatch(addCountAction)
}
}
}
class App extends React.Component {
render() {
return (
<div className="App">
{this.props.count}
<button onClick={() => this.props.addCount()}>增加</button>
</div>
);
}
}
export default connect(mapStateToProps, mapDispatchToProps)(App)