资讯专栏INFORMATION COLUMN

更简洁易用的 react 数据流 nearly-react

xushaojieaaa / 1720人阅读

摘要:一个简洁强大的数据流框架安装特性上图为架构图参考自在其基础上做了以下简化和改进功能上集成我们不再需要多写一个方法去异步获取数据更多情况下我们将使用让代码更加简洁的使用更加灵活的单实例和多实例使用能很巧妙地实现跨组件通信和通用组件控制逻

Nearly

一个简洁, 强大的数据流框架; Github

安装
npm install --save nearly-react
特性

上图为 flux 架构图, Nearly 参考自 flux, 在其基础上做了以下简化和改进:

功能上:

集成 Promise, 我们不再需要多写一个 componentDidMount 方法去异步获取数据, 更多情况下, 我们将使用 stateless component 让代码更加简洁;

Store 的使用更加灵活, Store 的单实例和多实例使用能很巧妙地实现跨组件通信和通用组件控制逻辑的复用;

相比 flux:

API 更加简洁, 在业务中一般只会用到 connectdispatch 方法;

对状态进行集中管理, 写法与原始的 React 相似, 学习和迁移成本低;

更轻量, min 后只有 6K;

使用示例
import React from "react";
import { render } from "react-dom";
import {connect, dispatch, registerStore} from "nearly-react";

registerStore("counter", {
    // 必须实现 init 方法, 它将被隐式调用, 作用是初始化 state
    init() {
        return {
            count: 0
        };
    },

   add(getState, step) {
       return {
           count: getState().count + step
       };
   }
};

let incr = () => dispatch("counter::add", 1);
let decr = () => dispatch("counter::add", -1);

function Counter(props) {
    return (
        
{props.count}
) } let HocCounter = connect("counter", Counter); render( , document.getElementById("root") )
API registerStore(storeName, dispatcherSet)

该方法将注册一个 Store, 需要注意的是该方法必须先 connect 执行, 例:

registerStore("customStore", {
    // 必须实现 init 方法
    init() {
        return {sum: 0};
    },
    add(getState, num) {
        return {sum: getState().sum + num};
    }
});
Dispatcher functions(getState, ...args)

registerStore 接受的第二个参数里的方法即 Dispatcher functions;
Dispatcher function 的第一个参数为 getState 方法, 该方法返回的永远是当前最新的 state, 其余参数为 dispatch 方法所传的参数;

对于 Dispatcher function 的返回值:

为普通对象时, 返回值直接 merge 进旧 state;

Promise 时, 取 Promise.prototype.then 方法里的参数 merge 进旧 state;

null 时, 不 merge, 不触发 render;

例:

registerStore("counter", {
    // 必须实现 init 方法, init 中也可以使用 Promise
    init() {
        return fetch("./test.json").then(res => res.json());
    },
    
    add(getState, step) {
        return {
            count: getState().count + step
        };
    },
   
   // 异步增加
    addAsync(getState, step) {
        return new Promise(resolve => {        
            setTimeout(() => {
                // getState 方法返回的永远是最新的 state
                let count = getState().count + step;
                resolve({count})
            }, 1000);
        });
    },

    // 不触发渲染
    nothing(getState, step) {
        return null;
    }
};
dispatch(action, ...args)

默认配置的 action 格式为 ${storeName}::${function},

dispatch 会根据 action 映射到相应的 Dispatcher function, 并将 args 作为参数传入 Dispatcher function, 将其返回的结果提交给 Store, 由 Store 触发组件更新;

connect(storeName, Component [, PlaceHolder, isPure])

该方法会根据 storeName 获得 Store, 再将 Store, ComponentPlaceHolder 组合, 返回一个高阶组件;

其中, PlaceHolder 为默认展示组件 (可选), 当且仅当 init 返回 Promise 时有效, 在 Component 被插入 dom 之前, 组合后的高阶组件会先展示 PlaceHolder 组件, 可用于实现 loading 之类的效果;

但组件过大时, 可以通过设置 isPure 为 true 来提高性能, 当设置 isPure 为 true 时, 只有 dispatch 方法能触发组件的 render, 我相信这比通过在 shouldComponentUpdate 里写 shallowEqual 要有效得多;

也可以通过下面的 configure 设置默认的 isPure 为 true;

进阶使用 dispatcher(action, ...args)

dispatch 的高阶函数; 例:

dispatch("counter::add", 1);
等同于: dispatcher("counter::add")(1);

dispatch("test::testAdd", 1, 2, 3, 4);
等同于: dispatcher("test::testAdd", 1, 2)(3, 4);
configure(option)

使用 nearly 进行开发, 我们需要考虑 storeName 重复的情况, 我推荐通过将 storeName 映射文件路径的方式来避免;

nearly 提供了两个可供配置的方法: beforeConnectbeforeDispatch;

beforeConnect 会在 connect 方法被调用之前调用, 接受的参数为传入 connect 方法的 storeName; 我们可以用它去加载对应的 JS 文件, 并注册 Store;

beforeDispatch 会在 dispatch 方法被调用之前调用, 接受的参数为传入 dispatch 方法的 action;

默认配置如下:

import {registerStore, getStore} from "./store";

let config = {
    // 默认的 isPure
    defaultPure: false,

    // 默认不开启自动注册 Store
    beforeConnect(storeName) {
        // let store = getStore(storeName);

        // if (!store) {
        //    let realName = storeName.split("#")[0];
        //    registerStore(storeName, require(`./actions/${realName}.js`));
        // }
    },

    beforeDispatch(action) {
        let [storeName, dispatcherName] = action.split("::");

        let store = getStore(storeName);
        if (!store) {
            throw Error(`store "${storeName}" does not exist`);
        }

        let dispatcher = store.dispatchers[dispatcherName];
        if (!dispatcher) {
            throw Error(`the module does not export function ${dispatcherName}`);
        }

        return {store, dispatcher};        
    }
}

使用示例:

import {configure, getStore, registerStore} from "nearly-react";

configure({
    beforeConnect(storeName) {
        // 配置 beforeConnect 方法, 自动注册 Store
        // 当 store 不存在时
        // 自动去 actions 目录下加载 JS 模块, 并注册 Store
        let store = getStore(storeName);

        if (!store) {
            let realName = storeName.split("#")[0];
            registerStore(storeName, require(`./actions/${realName}.js`));
        }
    }
});
同一 Store 单实例使用

在业务中我们经常需要跨组件通信, 或者组件间共享数据;

使用 Nearly 我们能很轻易地将两个不同的组件绑定相同的 Store, 只要传入 connectstoreName 是相同的即可;
例: 简单的输入同步显示

registerStore("vm", {
    // 必须实现 init 方法, 它将被隐式调用, 作用是初始化 state
    init() {
        return {
            value: ""
        };
    },

   change(getState, value) {
       return {
           return { value };
       };
   }
};

// /components/Input.js
let change = e => dispatch("vm::change", e.target.value);

function Input(props) {
    return 
}
export default connect(Input, "vm");


// /components/Text.js
function Text(props) {
    return 

{props.value}

} export default connect(Text, "vm");

详见示例: One-store

同一 Store 多实例使用

我们开发通用组件时会需要给同一组件绑定同一 store 的不同实例以复用; 可以通过给 storeName 加上 #id 来区分不同 Store;

// Dialog.js
export default function Dialog (props){
    return 
{props.content}
} let DialogA = connect(Dialog, "dialog#a"); let DialogB = connect(Dialog, "dialog#b"); // 关闭弹窗 A dispatch("dialog#a::close"); // 关闭弹窗 B dispatch("dialog#b::close");

注意, 当在组件内部使用 dispatch 时, 可以通过 props._storeName 来确定 storeName;

详见示例: Dialog

示例

TodoMVC

Counter

Dialog

One-store

React-SPA-Seed

Tips

nearly-config.js 必须在业务逻辑之前加载;

虽然有 registerStore API, 不过作者还是推荐使用 connect 来隐式注册 Store, 因为 connect 通过 storeName 映射文件的方式来注册 Store, 在确保唯一性的同时更容易维护和 debug;

在 Nearly 中对 Promise 的判断是不准确的 (只要有 then 方法均认为是 Promise 实例) , 一方面是因为 Nearly 中只使用了 then 方法, 另一方面是为了兼容 jQuery.Deferred 等类库;

欢迎提 issue 或是 pr;

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/83594.html

相关文章

  • 从零开始 React 组件开发之路 (一):表格篇

    摘要:下的表格狂想曲前言欢迎大家阅读从零开始的组件开发之路系列第一篇,表格篇。北京小李中的每一个元素是一列的配置,也是一个对象,至少应该包括如下几部分表头该列使用行中的哪个进行显示易用性与通用性的平衡易用性与通用性互相制衡,但并不是绝对矛盾。 React 下的表格狂想曲 0. 前言 欢迎大家阅读「从零开始的 React 组件开发之路」系列第一篇,表格篇。本系列的特色是从 需求分析、API 设...

    DesGemini 评论0 收藏0
  • 可能是基于 Hooks 和 Typescript 最好状态管理工具

    摘要:接上一篇我理想中的状态管理工具之前说,对于我个人来而言,理想的状态管理工具只需同时满足两个特点简单易用,并且适合中大型项目完美地支持未能找到一个完美满足这两点的,所以我决定自己造了一个叫。把分为和两类是很好的实践。 接上一篇:我理想中的状态管理工具 之前说,对于我个人来而言,理想的状态管理工具只需同时满足两个特点: 简单易用,并且适合中大型项目 完美地支持 Typescript 未...

    derek_334892 评论0 收藏0
  • GitHub 值得收藏前端项目[每月新...]

    摘要:也是一款优秀的响应式框架站点所使用的一套框架为微信服务量身设计的一套框架一组很小的,响应式的组件,你可以在网页的项目上到处使用一个可定制的文件,使浏览器呈现的所有元素,更一致和符合现代标准。 GitHub 值得收藏的前端项目 整理与收集的一些比较优秀github项目,方便自己阅读,顺便分享出来,大家一起学习,本篇文章会持续更新,版权归原作者所有。欢迎github star与fork 预...

    maxmin 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<