redux 作用
简单来说, redux 就是一个state容器。 类似于我们在开发web应用中,把数据挂载到windows对象上,然后取数据从windows中取, 但是这样容易污染上下文,同时数据变动不可预测。 而redux中自定义state,只能通过getState获取数据, 不会污染上下文。同时只能通过dispatch action来触发相应的reducer,数据变动可以预测(单向)。
redux 核心API
store 存储数据的容器
- store中常用api
- getState() // 获取store 中state
- dispatch(action) // 触发相应action
- subscribe(listener) // 订阅监听, 即监听dispatch触发
- ...
下面使用的例子, state 基本结构如下
{
todos: [{
text: 'Eat food',
completed: true
}, {
text: 'Exercise',
completed: false
}],
}
Reducers
function todos(state = [], action) {
switch (action.type) {
case 'ADD_TODO':
return state.concat([{ text: action.text, completed: false }])
default:
return state
}
}
reducer 是一个纯函数, 将state 和 action相关联, 接受(state, action) 作为参数, 返回一个新的state。
简单理解: 根据规则改变state
Actions
{ type: 'ADD_TODO', text: 'Go to swimming pool' }
action 是一个 plain object (普通对象)的形式,在使用中, 我们一般定义的格式: { type: '', payload: {}}
action定义了修改state的规则。 只有通过dispatch(触发) action 才能修改store中相应的state
使用redux
- store
import { createStore } from 'redux'
import todoReducer from '../reducer'
const store = createStore(todoReducer)
export default store
- reducer
const defaultState = [
{
text: "Eat food",
completed: true,
},
{
text: "Exercise",
completed: false,
},
]
const reducer = (state = defaultState, action) => {
const { type, payload } = action || {}
const { text } = payload || {}
switch (type) {
case 'ADD_TODO':
return state.concat([{ text, completed: false }])
default:
return state
}
}
export default reducer
- app.js
import React from 'react'
import store from './store'
import './App.css';
class App extends React.Component {
state = {
value: '',
}
componentDidMount() {
store.subscribe(() => {
this.forceUpdate()
})
}
handleConfirm = () => {
const { value } = this.state
if (value) {
const { dispatch } = store
dispatch({ type: 'ADD_TODO', payload: { text: value } })
this.setState({
value: ''
})
}
}
handleChange = (e) => {
this.setState({
value: e.target.value
})
}
render() {
const { value } = this.state
const { getState } = store
const todoList = getState()
return (
<div className="App">
<input value={value} onChange={this.handleChange} />
<button onClick={this.handleConfirm}>确定</button>
{(todoList || []).map((todoItem, index) => {
const { text } = todoItem || {}
return <div key={index}>
内容: { text }
</div>
})}
</div>
);
}
}
export default App;
- result
实现redux 核心api
- 实现createStore
createStore函数接受reducer, 返回一个store. 其中store包含dispatch, getState, subscribe等方法
const createStore = (reducer) => {
let currentState
let currentListeners = []
let nextListeners = currentListeners
const dispatch = (action) => {
currentState = reducer(currentState, action)
currentListeners = nextListeners
currentListeners.forEach(listener => listener())
return action
}
const getState = () => {
return currentState
}
const ensureCanMutateNextListeners = () => {
if (nextListeners === currentListeners) {
nextListeners = currentListeners.slice()
}
}
const subscribe = (listener) => {
nextListeners.push(listener)
ensureCanMutateNextListeners()
return () => {
ensureCanMutateNextListeners()
const index = nextListeners.indexOf(listener)
nextListeners.splice(index, 1)
currentListeners = null
}
}
const store = {
dispatch,
getState,
subscribe,
}
dispatch({type: 'init_____'})
return store
}
export {
createStore
}
现在将 上面实例中的redux 引用换成我们自己写的, 效果是一样的。
- 实现combineReducers
在实际中, 我们不会只存在一个reducer, 而是有多个reducer, 所以combineReducers 方法提供将多个reducer包裹起来执行, 返回一个新reducer函数
const combineReducers = (reducers) => {
const reducerKeys = Object.keys(reducers)
const finalReducers = {}
for(let i = 0; i < reducerKeys.length; i++) {
const key = reducerKeys[i]
if (typeof reducers[key] === 'function') {
finalReducers[key] = reducers[key]
}
}
const finalReducerKeys = Object.keys(finalReducers)
return (state = {}, action) => {
const nextState = {}
let hasChanged = false
for (let i = 0; i < finalReducerKeys.length; i++) {
const key = finalReducerKeys[i]
const reducer = finalReducers[key]
const prevStateForKey = state[key]
const nextStateForKey = reducer(prevStateForKey, action)
nextState[key] = nextStateForKey
hasChanged = hasChanged || nextStateForKey !== prevStateForKey
}
hasChanged = hasChanged || finalReducerKeys.length !== Object.keys(state).length
return hasChanged ? nextState : state
}
}
案例中增加一个recuder(只是例子,现实中不会这么写), 用于描述todoList 个数
countReducer.js
const countReducer = (state = 2, action) => {
const {type, payload} = action
switch (type) {
case 'ADD': return state + payload
default: return state
}
}
export default countReducer
修改app.js
...
handleConfirm = () => {
const { value } = this.state
if (value) {
const { dispatch } = store
dispatch({ type: 'ADD', payload: 1})
dispatch({ type: 'ADD_TODO', payload: { text: value } })
this.setState({
value: ''
})
}
}
...
render() {
const { value } = this.state
const { getState } = store
const { todosReducer: todoList, countReducer: count } = getState()
return (
<div className="App">
<input value={value} onChange={this.handleChange} />
<button onClick={this.handleConfirm}>确定</button>
<div>{count}</div>
{(todoList || []).map((todoItem, index) => {
const { text } = todoItem || {}
return <div key={index}>
内容: { text }
</div>
})}
</div>
);
}
- 实现增强createStore
createStore接受第二个参数enhancer。 现实中我们可能需要查看log日志,同时我们触发的dispatch 可能是异步的, 但是上面实现中, dispatch 只能接受普通的对象。 我们通过middleware 增强dispatch, 让dispatch支持接受函数。
我们需要安装 redux-thunk(支持异步), redux-logger(支持日志)。 因为这些middleware 不在redux 范围内, 所以这次我们不实现, 源码也很简单, 可以参考github 上面
compose.js
function compose(...funcs) {
if (funcs.length === 0) {
return (arg) => arg
}
if (funcs.length === 1) {
return funcs[0]
}
return funcs.reduce((a, b) => (...args) => a(b(...args)))
}
export default compose
applyMiddleware.js
import compose from '../compose'
function applyMiddleware(...funcs) {
return (createStore) => {
return (reducer) => {
const store = createStore(reducer)
let dispatch = store.dispatch
const middlewareApi = {
getState: store.getState,
dispatch: (action, ...args) => dispatch(action, ...args)
}
const chain = funcs.map(func => func(middlewareApi))
dispatch = compose(...chain)(dispatch)
return {
...store,
dispatch
}
}
}
}
export default applyMiddleware
更新store.js
import logger from "redux-logger"
import thunk from "redux-thunk"
import { createStore, applyMiddleware } from '../cRedux'
import reducers from '../reducer'
const enhancer = applyMiddleware(thunk, logger)
const store = createStore(reducers, enhancer)
export default store
更新app.js
handleConfirm = () => {
const { value } = this.state;
if (value) {
const { dispatch } = store;
// dispatch({ type: "ADD_TODO", payload: { text: value } });
// 使用redux-thunk 支持异步
dispatch((dispatch, getState) => {
setTimeout(() => {
dispatch({ type: "ADD_TODO", payload: { text: value } });
}, 1000)
})
dispatch({ type: "ADD", payload: 1 })
this.setState({
value: "",
});
}
};