摘自: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)