本人是一个 dva 框架的长期使用者, 个人非常喜欢 dva 对于 redux 的封装, 但是在使用 dva 的过程中遇到了许多不是很顺手的问题, 也因此产生了自己动手编写一套类 dva 库, 并致力于解决 dva 使用过程中的所表现出来的不足, 稍后我会介绍在使用过程中遇到的问题, 并阐述 zoro 中如何解决该问题.
本文章适合有一定 redux 基础或者使用过 dva 的读者
简述
极简的 API, 可快速上手
简化 redux 的应用难度
快速接入微信原生小程序, wepy 小程序框架, taro 小程序框架中, 原则上能使用 redux 的地方皆可以接入
内置插件, 支持自定义编写插件
全局错误处理方案
以下列出相关资料链接:
https://github.com/FaureWu/zoro GitHub 仓库地址
https://github.com/FaureWu/zoro-plugin 相关插件 GitHub 仓库地址
https://github.com/reduxjs/redux redux 仓库地址
https://github.com/dvajs/dva dva 库链接
zoro 名字的由来
zoro 全名 roronoa zoro(罗罗诺亚. 索隆), 是动漫 onepiece 中的一个剑客, 他自身没有恶魔果实的, 确不断的朝着世界第一大剑豪的目标前进, 最终也证明了他在不断的成长
为什么编写 zoro 库
我主要列举一下本人在使用 dva 过程中遇到过的不足, 纯属个人观点, 如有错误, 还望指出
1. dva 在外部调用 model 时 dispatch 需要给上 type 类型, 不够直观
首先我们来看一下 dva 给出的列子:
- import React from 'react'
- import { connect } from 'dva'
- import ProductList from '../components/ProductList'
- const Products = ({ dispatch, products }) => {
- function handleDelete(id) {
- // dva 中调用 model action or effect 的办法
- dispatch({
- type: 'products/delete', // 需指定 model namespace
- payload: id,
- })
- }
- return (
- <div>
- <h2>List of Products</h2>
- <ProductList onDelete={handleDelete} products={products} />
- </div>
- )
- }
- export default connect(({ products }) => ({
- products,
- }))(Products)
而我希望在 zoro 中实现的使用方式如下:
- import React from 'react'
- import { connect } from 'react-redux'
- import { dispatcher } from '@opcjs/zoro'
- import ProductList from '../components/ProductList'
- const Products = ({ dispatch, products }) => {
- function handleDelete(id) {
- // 区别主要看这里
- dispatcher.products.delete(id)
- }
- return (
- <div>
- <h2>List of Products</h2>
- <ProductList onDelete={handleDelete} products={products} />
- </div>
- );
- };
- export default connect(({ products }) => ({
- products,
- }))(Products);
2. dva 引入了 saga 库作为基础, 增加了入门门槛,(虽然也没有多少难度)
dva 中 model 定义方式
- export default {
- namespace: 'products',
- state: [],
- effects: {
- *delete({ payload: { id } }, { call, put }) {
- yield call(deleteProductFromServer, { id })
- yield put({ type: 'delete', payload: { id } })
- },
- }
- reducers: {
- delete(state, { payload: id }) {
- return state.filter(item => item.id !== id)
- },
- },
- };
zoro 中引入 es7 语法中的 async, await 代替 saga
- export default {
- namespace: 'product',
- state: [],
- effects: {
- async delete({ payload: { id } }, { put }) {
- await deleteProductFromServer({ id })
- put({ type: 'delete', payload: { id } })
- },
- },
- reducers: {
- delete({ payload: id }, state) {
- return state.filter(item => item.id !== id)
- },
- },
- }
3. dva 中的 put 中的同步执行在 dva1 中无法实现, 在 dva2 中作者提供了相应解决办法, 但依旧麻烦
这里是 dva2 中的实现方式, 通过 take 等待上一个 put 执行完毕, 再继续执行,
- yield put({
- type: 'addDelay', payload: {
- amount: 2
- }
- })
- yield take('addDelay/@@end')
- const count = yield select(state => state.count)
- yield put({
- type: 'addDelay', payload: {
- amount: count, delay: 0
- }
- })
zoro 中采用 async,await, 天生所有的 effect 方法都是一个 promise 对象, 因此也支持 put 的同步执行
- await put({
- type: 'addDelay', payload: {
- amount: 2
- }
- })
- const count = select().count
- await put({
- type: 'addDelay', payload: {
- amount: count, delay: 0
- }
- })
如何在 react 应用中快速接入 zoro
如果你是一个 dva 使用者, 或者你是个前端大牛, 下面的介绍可以忽略, 直接去仓库查看 API 文档, 那里有更全面的 API 介绍
接入代码如下
- import React from 'react'
- import { render } from 'react-dom'
- import { Provider } from 'react-redux'
- import zoro from '@opcjs/zoro'
- import testModel from './models/test'
- import App from './components/App'
- const App = zoro()
- App.model(testModel) // 注册单个 model
- // App.model([model1, model2]) // 注册多个 model
- const store = App.start() // 启动并创建 store
- render(
- <Provider store={store}>
- <App />
- </Provider>,
- document.getElementById('root')
- )
从代码中可以很直观的发现, 接入 zoro 到 react 应用只需简单的 4 个步骤即可
- import zoro from '@opcjs/zoro'
- const App = zoro()
引入 zoro 库, 并创建 App
- import testModel from './models/test'
- App.model(testModel)
为 App 引入 model, 这里先忽略 model 改如何定义, 后续会着重介绍
const store = App.start()
生成 redux store 对象用于注入到界面层中
- render(
- <Provider store={store}>
- <App />
- </Provider>,
- document.getElementById('root')
- )
将生成的 store, 通过 react-redux Provider 注入到界面组件中
何为 model, 以及我们该如何定义我们的 model
model 是用于存储界面数据的, 同时也用于管理数据, 包括增删改查, 异步获取数据等, 下面我将会以 todos 应用来阐述如何定义我们的 model
最简单的 model 应用
首先我们需要实现的功能如下, 显示 todo 列表, 删除
明白了需求, 首先我们定义 todos 的 model
- export default {
- namespace: 'todos', // 给 model 取一个名称, 必须是唯一的不变的
- state: [], // model 初始值, 可以是任意类型
- reducers: {
- add({ payload: { text } }, state) {
- return state.concat([text])
- },
- },
- }
注册我们的 model 到 App 中
- import todos from '../models/todos'
- App.model(todos)
... 省略其他步骤, 详见上一节
接下来我们需要完善我们 Todos 组件
- import React from 'react'
- import { connect } from 'react-redux'
- // 引入 dispatcher
- import { dispatcher } from '@opcjs/zoro'
- const Todos = ({ todos }) => {
- // 触发一个 todos 的添加事件
- const handleAdd = () => dispatcher.todos.add({ text: '这是一个新增的代办事件' })
- return (
- <div>
- <ul>
- {todos.map(todo => (
- <li key={todo}>todo</li>
- ))}
- </ul>
- <button onClick={handleAdd}> 添加 </button>
- </div>
- )
- }
- // 通过 connect 链接 state 数据到组件中
- export default connect(({ todos }) => ({
- todos,
- }))(Todos)
只需最后一步我们便完成了这个需求, 链接 Todos 组件到 App 组件中
- import React from 'react'
- import Todos from '../components/Todos'
- const App = () => <Todos />
- export default App
到这里一个最简单的 todos 需求便完成了, 我们对 model 的基本定义也有了大体了解, 假如我们需要在应用初始时获取服务器上的 todo 列表展示呢, 该如何完成呢
如何通过 model 与服务器实现交互
首先我们为 model 新增获取 todos 的服务
- export default {
- namespace: 'todos', // 给 model 取一个名称, 必须是唯一的不变的
- state: [], // model 初始值, 可以是任意类型
- reducers: {
- add({ payload: { text } }, state) {
- return state.concat([text])
- },
- // 新增 update reducers
- update({ payload }) {
- return payload
- },
- },
- effects: {
- // 新增 queryTodos 从服务器中获取列表数据
- async queryTodos({ payload }, { put, select }) {
- // 省略 getTodosFromServer, 这是一个 Ajax 请求, 如 axios, fetch 等
- // 与服务器通信, 返回列表, 该函数必须返回 Promise 对象
- const { todos } = await getTodosFromServer(payload)
- // 通过 select 获取当前的 todos 列表, select 用法详见 API 文档
- const currentTodos = select()
- // put 功能很多, 可以发起本 model 的 reducer,effect
- // 也可以发起外部 model, 但需指定 namespace
- // 如 put({ type: 'test/update' }) 发起 test model 中的某个方法
- // 如果 put 发起的是一个异步的 effect, 必要时可以通过 await 等待结果
- // 更多用法详见 API 文档
- put({ type: 'update', payload: currentTodos.concat(todos) })
- },
- },
- }
定义好了 model, 接下来我们我们只需在组件挂载时调用该方法即可
- import React from 'react'
- import { connect } from 'react-redux'
- // 引入 dispatcher
- import { dispatcher } from '@opcjs/zoro'
- class Todos extend React.Component {
- componentDidMount() {
- // 初始化挂载时调用获取异步数据
- dispachter.todos.queryTodos()
- }
- handleAdd() {
- dispatcher.todos.add({ text: '这是一个新增的代办事件' })
- }
- render () {
- return (
- <div>
- <ul>
- {this.props.todos.map(todo => (
- <li key={todo}>todo</li>
- ))}
- </ul>
- <button onClick={handleAdd}> 添加 </button>
- </div>
- )
- }
- }
- // 通过 connect 链接 state 数据到组件中
- export default connect(({ todos }) => ({
- todos,
- }))(Todos)
到这里一个简单的 zoro 接入教程就结束了, 其中还有很多好玩有用的特性没能介绍, 比如插件机制, 全局错误捕获等, 这是我第一个写教程, 不知调理是否清晰易懂, 望各位看官轻喷, 有问题请指正, 让我们共同进步, 不断的造出更多更简易的轮子
来源: http://www.jianshu.com/p/19f852252500