目的:打造一个简单的 redux
数据流方案,实现功能相似与 dva
,但仅仅只是对 redux 进行封装,简化 redux 使用流程和难度。最终目的确定是为了提高开发效率和加深本身对 redux 源码的理解能力和运用能力css
若是你对 redux 理解还不够深刻,想要彻底理解它,能够看一下这篇文章:彻底理解 redux(从零实现一个 redux)html
仓库名称叫 demacia
,有没有熟悉的既视感,对,就是德玛西亚,命名原因:没啥原因,英雄联盟只玩过德玛西亚,玩过几回,王者荣耀常玩英雄-亚瑟(2016 年刚毕业连续玩了两百把 😂)。react
编写 redux 部分的方式和 dva 相似,主要是引入方式和使用方式有所区别css3
进入本身的 react 项目,经过 npm 安装 demaciagit
npm install demacia -S
复制代码
在 src 下建立一个 store 文件用于建立仓库github
// src/store/index.js
import { demacia } from 'demacia'
// 这里引入了一个名为global的model
import global from './global'
// 须要初始化建立的model
const initialModels = {
global
}
// 设置state初始值,用于全局初始化数据,好比当须要持久化存储时,会很方便
const initialState = {
global: {
counter: 2
}
}
// 调用demacia并传入初始参数,返回了redux的store
const store = demacia({
initialModels,
initialState,
middlewares: [], // 加入中间件
effectsExtraArgument: {} // 额外参数
})
export default store
复制代码
上面的代码中,咱们引入了 demacia
函数,并调用它,而后返回了 store
,这个 store
就是调用 redux
的 createStore
函数生成的,咱们在调用 demacia
函数时传入了一个对象做为参数,并包含了两个初始化属性,initialModels
用于注入 model
数据,initialState
用于设置 redux
初始 state
。npm
模块 global.js 代码以下redux
// src/store/模块global.js
export default {
namespace: 'global',
state: {
counter: 0
},
reducers: {
increment(state, { payload }) {
return {
...state,
counter: state.counter + 1
}
},
decrement(state, { payload }) {
return {
...state,
counter: state.counter - 1
}
}
},
effects: {
async add({ dispatch }, { payload }) {
const res = await new Promise(resolve => {
setTimeout(() => {
resolve({ code: 1, success: true })
}, 1000)
})
dispatch({
type: 'increment',
payload: res
})
}
}
}
复制代码
使用react-redux
把 store 加入项目,这里跟 redux 同样api
// src/App.js
import React from 'react'
import { Provider } from 'react-redux'
import { HashRouter } from 'react-router-dom'
import store from './store'
import routes from './routes'
function App() {
return (
<Provider store={store}> <HashRouter>{routes}</HashRouter> </Provider>
)
}
复制代码
页面中使用react-redux
的connect
方法获取 state数组
// src/pages/home/index.js
import React from 'react'
import { Button } from 'antd'
import { connect } from 'react-redux'
const HomePage = props => {
return (
<div> <div>globalCounter: {props.global.counter}</div> <Button onClick={() => { props.dispatch({ type: 'global/increment' }) }} > 同步increment </Button> <Button onClick={() => { props.dispatch({ type: 'global/add' }) }} > 异步increment </Button> </div>
)
}
export default connect(state => state)(HomePage)
复制代码
触发同步或者异步操做,都经过dispatch
来分发对应模块对应的action
或effects action
上面使用的都是全局的 state,若是某个页面或者某个组件想有直接的状态呢,或者说是动态的向 store 添加 state 和 reducer,这时候能够引入 model
来进行处理。
下面建一个页面 Todos,实现一个 todo list
编写输入 Todos 的 model:
// src/pages/todos/model.js
import { model } from 'demacia'
export default model({
namespace: 'Todos',
// 至关于react-redux中的connect的第一个参数,会传入state,返回的对象会返回给组件的props
selectors: function(state) {
return {
todos: state.Todos.todos,
loading: state.Todos.loading,
total: state.Todos.todos.reduce((acc, item) => acc + (item.count || 0), 0)
}
},
state: {
todos: [{ name: '菠萝', id: 0, count: 2 }]
},
reducers: {
putTodos(state, { payload }) {
return {
...state,
todos: [...state.todos, ...payload]
}
},
putAdd(state, { payload }) {
return {
...state,
todos: [...state.todos, payload]
}
}
},
effects: {
async getTodos({ dispatch }) {
const { datas } = await new Promise(resolve => {
setTimeout(() => {
resolve({
code: 0,
datas: [
{ name: '🍎', id: 1, count: 11 },
{ name: '🍆', id: 2, count: 22 }
]
})
}, 1000)
})
dispatch({ type: 'putTodos', payload: datas })
},
async add({ dispatch }, { payload }) {
const { code } = await new Promise(resolve => {
setTimeout(() => {
resolve({
code: 0
})
}, 200)
})
if (code === 0) {
dispatch({ type: 'putAdd', payload: payload })
}
}
}
})
复制代码
上面须要注意的几点:
一,model 函数接收了如下参数:
namespace
reducer 的名称,给 combineReducers
合并 reducers 用的selectors
至关于 react-redux 中的 connect 的第一个参数(mapStateToProps),会传入 state,须要返回一个对象,并合并到组件的 propsstate
存储的数据,从上方代码selectors
上能够看出多 state 中出现了loading
,loading
是内置的,它是一个数组,存储正在执行中的effects
键reducers
存储的是一个对象,对象的键是 action 的 type,值是一个函数,接收两个参数:state 和 action 对象,执行 reducer 过程当中须要执行的部分,函数的返回值是新的 state
effects
处理反作用的地方,每个属性都必须函数,相似前面的reducers
,接收两个参数:
dispatch
和 state,以及一些扩展(初始化的时候传入的effectsExtraArgument
对象就会合并到这个参数里面)payload
属性二,model 函数执行后返回了一个高阶函数
组件引入 model,用 model 函数执行后返回的高阶函数包裹组件,这里会作三件事:
selectors
执行的结果返回的对象加入到组件的 propseffects
对象结构注入到组件的 propsresetStore
和 setStore
方法注入到组件的 props,执行是分别会触发两个 action,resetStore
的 action 会把数据重置为最初的数据,setStore
须要开发者在对应的reducers
里自定义一个setStore
函数下面是编写页面,引入 model 后能够获取数据并实现一些功能:
// src/pages/todos/index.js
import React, { useEffect, useState } from 'react'
import model from './model'
const Todos = props => {
const { todos = [], total, getTodos, loading } = props
const [input, setInput] = useState('')
useEffect(() => {
getTodos()
}, [getTodos])
return (
<div> <h2>水果蔬菜(total: {total})</h2> <div> <input value={input} onChange={e => setInput(e.target.value)} /> <button onClick={async () => { await props.add({ name: input, id: Math.random() .toString(16) .slice(2), count: parseInt(Math.random() * 10) }) setInput('') }} > 添加 </button> </div> {loading.includes('getTodos') ? ( 'loading...' ) : ( <ul> {todos.map(fruit => ( <li key={fruit.id}>{fruit.name}</li> ))} </ul> )} <div> <button onClick={() => { props.resetStore() }} > resetStore </button> <button onClick={() => { props.setStore('haha') }} > setStore </button> </div> </div> ) } export default model(Todos) 复制代码
从上面能够看出,demacia
只有两个 api:
demacia
用于初始化 store 用的,能够接收四个参数
{ [model对应的namespace]: model对应的state初始值 }
建立 store
部分model
用于生成 reducer,并把新的 reducer 合并到项目的 reducers 中,使用方法上面有讲到,参考上面的应该就能够了扩展:
功能主要是有
resetStore
重置 state源码地址: github.com/ht113158958…
Demo 地址: github.com/ht113158958…
附上一些以前写过的相关文章地址:
实现过程参考的资料: