# natur 使用手册

# 基本介绍

  1. 这是一个简洁、高效的react状态管理器
  2. 良好的typescript体验
  3. 单元测试覆盖率99%,放心使用
  4. 包体积,minizip 5k(uglify+gzip压缩后5k)
  5. 如果你的环境是react@17或者更低版本,你可以使用natur@2.1.x

# 起步

  1. 打开你的react项目
  2. 安装natur
yarn add natur
# npm install natur -S

# 设计

# 模块流程

模块

# 模块管理

natur本身是一个模块管理器,外加发布订阅

store

# 简单的示例

在线体验 (opens new window)

# 声明模块

const count = {
  // 存放数据
  state: {
    number: 0,
  },
  // state的映射
  maps: { 
    isEven: ['number', number => number % 2 === 0],
  },
  // actions用来修改state
  actions: { 
    inc: number => ({number: number + 1}),
    dec: number => ({number: number - 1}),
  }
}

# 创建store和inject

import { createStore, createUseInject } from 'natur';

const store = createStore({count}, {});
const useInject = createUseInject(() => store);
const useFlatInject = createUseInject(() => store, {flat: true});

# 在React中使用

// 创建一个count模块的注入器

// 声明props类型
const App = () => {
  const [count] = useInject('count');
  const [flatCount] = useFlatInject('count');
  // flatCount中的数据是扁平的,即state、action、maps中的数据被放到一个对象中(state数据会被maps覆盖,请注意命名)

  return (
    <>
      <button onClick={() => count.actions.dec(count.state.number)}>-</button>
      <span>{count.state.number}</span>
      <button onClick={() => count.actions.inc(count.state.number)}>+</button>
      <span>{count.maps.isEven}</span>
    </>
  )
};

ReactDOM.render(<App />, document.querySelector('#app'));

# module详解

一个模块由state, maps, actions构成

# state — 存储数据

  • 必填:true
  • 类型:any
  • state用来存储数据

# maps — 计算属性

  • 必填:false

  • 类型:{[map: string]: Array<string|Function> | Function;}

  • maps是state数据的映射,它的成员必须是一个数组或者函数,我们暂且称其为map

  • 如果map是数组,前面的元素都是在声明此map对state的依赖项。最后一个函数可以获取前面声明的依赖,你可以在里面实现你需要的计算的逻辑。在组件中,你可以获取数组最后一个函数运行的结果。

  • 如果map是函数,那么它只能接受state作为入参,或者没有参数,如果是state作为参数,那么当state更新时,此map一定会重新执行,没有缓存。如果map没有参数,那么此map只会执行一次

  • maps的结果是有缓存的,如果你声明的依赖项的值没有变化,那么最后一个函数便不会重新执行

  • 什么时候需要手动声明依赖?如果你的map逻辑较为复杂,或者你的map返回值不是基本类型的值,需要给到组件渲染,那么你可以考虑手动声明依赖,保证性能。一般直接使用函数的方式即可。

const demo = {
  state: {
    number: 1,
    value: 2
  },
  maps: {
    // 数组前面的元素,都是在声明此map对state的依赖项,最后一个函数可以获取前面声明的依赖,你可以在里面实现你想要的东西
    sum1: ['number', 'value', (number, value) => number + value],
    // 你也可以通过函数的方式声明依赖项,这对于复杂类型的state很有用
    sum2: [state => state.number, s => s.value, (number, value) => number + value],
    // 也可以是个函数,直接依赖整个state,缺点是只要state更新就会重新执行函数,没有缓存
    sum3: ({number, value}) => number + value,
    // 也可以是个函数,没有依赖,只执行一次
    isTrue: () => true,
  },
}
/**
 * 在组件中你获得的数据为
 * demo: {
 *  state: {
 *    number: 1,
 *    value: 2,
 *  }
 *  maps: {
 *    sum1: 3,
 *    sum2: 3,
 *    sum3: 3,
 *    isTrue: true
 *  }
 * ...
 * }
 */

# actions — 更新数据

  • 必填:true
  • 类型:{[action: string]: (...arg: any[]) => any;}
  • actions的成员必须是函数,如果不设置中间件,那么它返回的任何数据都会作为新的state,并通知使用此模块的react组件更新,这是在natur内部完成的。
  • actions必须遵照immutable规范!
const demo = {
  state: {
    number: 1,
  },
  // actions用来修改state。它返回的数据会作为新的state(这部分由natur内部完成)
  actions: { 
    inc: number => ({number: number + 1}),
    dec: number => ({number: number - 1}),
  }
}

/**
 * 在组件中你获得的数据为
 * demo: {
 *  state: {
 *    number: 1,
 *  }
 *  actions: {
 *    inc: (number) => 新的state,
 *    dec: (number) => 新的state,
 *  }
 * ...
 * }
 */

# watch — 监听模块变动

  • 必填:false
  • 类型:{[moduleName: string]: (event: WatchEvent, api: WatchAPI) => void;} | (event: AllModuleEvent, api: WatchAPI) => void;
  • watch可以监听某个模块的变动,也可以监听所有模块的变动
  • 在watch里面可以获取当前模块的state,maps,也可以调用localDispatch来调用本模块的action
const demo = {
  state: {
    number: 1,
  },
  actions: { 
    inc: number => ({number: number + 1}),
    dec: number => ({number: number - 1}),
  },
  watch: {
    moduleA: (event: ModuleEvent, api: WatchAPI) => {
      // 任何 moduleA 的变动都会触发这个函数,具体的变动信息在event参数获取
      // 例如moduleA的初始化,更新state,销毁都可以监听到
      // api参数包含demo模块的, getState, getMaps, localDispatch等API, 以及获取全局store的getStoreAPI.
      // localDispatch只能调用本模块的action,例如:localDispatch('inc', 2);
    }
  }
}

const moduleA = {
  state: {
    number: 1,
  },
  actions: { 
    inc1: number => ({number: number + 1}),
    dec1: number => ({number: number - 1}),
  },
  // watch也可以是一个函数用来监听所有模块的变动
  watch: (event: AllModuleEvent, api: WatchAPI) => { 
      // 任何模块的变动都会触发这个函数,包括moduleA模块自己,具体的变动信息在event参数获取
      // api参数包含moduleA模块的, getState, getMaps, localDispatch等API, 以及获取全局store的getStoreAPI.
      // localDispatch是只能调用本模块的action,例如:localDispatch('inc1', 2);
    }
}

# 应用场景

# 注入多个模块

// 导入你之前创建的inject函数,详情请参考上面的简单例子
import useInject from 'your-use-inject';

// 在你的组件中
const [module1] = useInject('module1');
const [module2] = useInject('module2');
// ...

# 同步更新数据

const app = {
  state: {
    name: "tom",
  },
  actions: {
    // 这里是同步更新state中的name数据
    changeName: newName => ({ name: newName }),
  }
};

# 异步更新数据

const app = {
  state: {
    name: "tom",
  },
  actions: {
    // 这里是异步更新state中的name数据
    changeName: newName => Promise.resolve({ name: newName }),
  }
};

# 异步多批次更新数据

import { ThunkParams } from "natur/dist/middlewares";

const state = {
  now: Date.now(),
}
const actions = {
  // 这里是异步多批次更新state中的name数据
  updateNow: () => ({setState}: ThunkParams<typeof state>) => {
    // 每秒更新一次now的值
    setInterval(() => setState({now: Date.now()}), 1000);
  },
}

const app = {
  state,
  actions
};

# 在actions中获取最新的state,maps值

import { ThunkParams } from "natur/dist/middlewares";

const state = {
  name: 'tom',
}
const maps = {
  nameIsTome: ['name', (name: string) => name === 'tom'],
}

const actions = {
  updateName: () => ({getState, getMaps}: ThunkParams<typeof state, typeof maps>) => {
    // 获取最新的state值
    const currentState = getState();
    // 获取最新的maps值
    const currentMaps = getMaps();
  },
}

const app = {
  state,
  maps,
  actions
};

# 在actions中调用其他的action

import { ThunkParams } from "natur/dist/middlewares";

const state = {
  name: 'tom',
  loading: true,
}

const actions = {
  loading: (loading: boolean) => ({loading}),
  fetchData: (newName: string) => async ({localDispatch}: ThunkParams) => {
    // 调用loading方法
    localDispatch('loading', true);
    await new Promise(resolve => setTimeout(resolve, 3000));
    localDispatch('loading', false);
    return {name: newName};
  },
}

const app = {
  state,
  actions
};

# 组件只监听部分数据的变更

// 导入你之前创建的inject函数,详情请参考上面的简单例子
import useInject from 'your-use-inject';

// 这里App组件只会监听app,state中name的变化,其他值的变化不会引起App组件的更新
const [app] = useInject('app', {
  state: ['name'], // 也可以使用函数声明 state: [s => s.name]
});

// 这里App组件只会监听app,maps中deepDep的变化,其他值的变化不会引起App组件的更新
const [app] = useInject('app', {
  maps: ['deepDep'], 
}); 
// 这里App组件不论app模块发生什么变化,都不会更新
const [app] = useInject('app', {});


// 因为actions在创建后会保持不变,所以你不必监听它的变化
const App = () => {
  const [app] = useInject('app');
  // 获取注入的app模块
  const {state, actions, maps} = app;
  return (
    <input
      value={state.name} // app中的数据
      onChange={e => actions.changeName(e.target.value)}
    />
  )
};


# 懒加载模块配置

/*
  module1.js
  export {
    state: {
      count: 1,
    }
    actions: {
      inc: state => ({count: state.count + 1}),
    }
  }
  
*/
const otherLazyModules = {
  // module2: () => import('module2');
  // ...
}
const module1 = () => import('module1'); // 懒加载模块

// 创建store实例
// 第二参数就是懒加载的模块;
const store = createStore(
  { app }, 
  { module1, ...otherLazyModules }
);

// 然后用法等同于第二步

# 初始化state

import { createStore } from 'natur';
const app = {
  state: {
    name: 'tom',
  },
  actions: {
    changeName: newName => ({ name: newName }),
    asyncChangeName: newName => Promise.resolve({ name: newName }),
  },
};
/*
  createStore第三个参数
  {
    [moduleName: ModuleName]: Require<State>,
  }
*/
const store = createStore(
  { app }, 
  {},
);

store.globalSetStates({
  app: {name: 'jerry'} // 设置app 模块的state
})

export default store;

# 跨模块的交互的复杂业务场景

你可以使用模块中的watch功能,他可以监听任何模块的任何变动,并且你可以发起你想要的dispatch

import { ModuleEvent, AllModuleEvent, WatchAPI } from 'natur';

export const moduleA = {
    state: {},
    actions: {/* ... */},
    watch: {
        moduleB(event: ModuleEvent, api: WatchAPI) {
          // 任何 moduleB 的变动都会触发这个函数,具体的变动信息在event参数获取
          // api参数包含本模块的, getState, getMaps, localDispatch等API, 以及获取全局store的getStoreAPI.
          // localDispatch是只能调用本模块的action,例如:localDispatch('actionNameA', ...actionAArgs);
        }
    }
}
export const moduleB = {
    state: {},
    actions: {/* ... */},
    // watch也可以是一个函数用来监听所有模块的变动
    watch: (event: AllModuleEvent, api: WatchAPI) => { 
      // 任何模块的变动都会触发这个函数,具体的变动信息在event参数获取
      // api参数包含本模块的, getState, getMaps, localDispatch等API, 以及获取全局store的getStoreAPI.
      // localDispatch是只能调用本模块的action,例如:localDispatch('actionNameA', ...actionAArgs);
    }
}

# 在react之外使用natur

// 引入之前创建的store实例
import store from 'my-store-instance';

/*
  获取注册的app模块, 等同于在react组件中获取的app模块
  如果你想要获取懒加载的模块,
  那么你必须确定,这个时候该模块已经加载好了
*/
const app = store.getModule('app');
/*
  如果你确定,懒加载模块,还没有加载好
  你可以监听懒加载模块,然后获取
*/
store.subscribe('lazyModuleName', () => {
  const lazyModule = store.getModule('lazyModuleName');
});

/*
state: {
  name: 'tom'
},
actions: {
  changeName,
  asyncChangeName,
},
maps: {
  splitName: ['t', 'o', 'm'],
  addName: lastName => state.name + lastName,
}
*/


/*
  当你在这里使用action方法更新state时,
  所有注入过app模块的组件都会更新,
  并获取到最新的app模块中的数据,
  建议不要滥用
*/
app.actions.changeName('jerry');
// 等同于
store.dispatch('app', 'changeName', 'jerry');

/**
 * 
 * type: 模块变动的类型
 * init: 模块初始化事件
 * update: 模块state更新事件
 * remove: 模块移除事件
 * 
 * actionName: 模块更新state时的action名字
 */
type ModuleEvent = {
	type: 'init' | 'update' | 'remove',
	actionName?: string,
};
// 监听模块变动
const unsubscribe = store.subscribe('app', (me: ModuleEvent) => {
  // 这里可以拿到最新的app数据
  store.getModule('app');
});


// 取消监听
unsubscribe();

# 手动导入模块

// initStore.ts
import { createStore } from 'natur';

// 在实例化store的时候,没有导入懒加载模块
export default createStore({/*...modules*/}, {});

// ================================================
// lazyloadPage.ts 这是一个懒加载的页面
import store from 'initStore.ts'

const lazyLoadModule = {
  state: {
    name: 'tom',
  },
  actions: {
    changeName: newName => ({ name: newName }),
  },
  maps: {
    nameSplit: state => state.name.split(''),
    addName: state => lastName => state.name + lastName,
  },
};
/*
手动添加模块,在此模块被添加之前,其他地方无法使用此模块
要想其他地方也使用,则必须在store实例化的时候就导入
*/
store.setModule('lazyModuleName', lazyLoadModule);

const lazyLoadView = () => {
  // 现在你可以获取手动添加的模块了
  const {state, maps, actions} = store.getModule('lazyModuleName');
  return (
    <div>{state.name}</div>
  )
}

# dispatch

import { createStore, inject, InjectStoreModule } from 'natur';

const count = {
  state: { // 存放数据
    number: 0,
  },
  maps: { // state的映射。比如,我需要知道state中的number是否是偶数
    isEven: ['number', number => number % 2 === 0],
  },
  actions: { // 用来修改state。返回的数据会作为新的state(这部分由natur内部完成)
    inc: number => ({number: number + 1}),
    dec: number => ({number: number - 1}),
  }
}

// 创建store这一步需要在渲染组件之前完成,因为在组件中,需要用到你创建的store
const store = createStore({count}, {});

const {actions, state} = store.getModule('count')

actions.inc(state.number);
// 等于
store.dispatch('count', 'inc', state.number);

# 不想写那么多typescript代码? (NaturBaseFactory)

  • 不想写maps那么多类型?

    import { NaturBaseFactory } from 'natur';
    
    const state = {
      count: 1,
    };
    
    const createMap = NaturBaseFactory.mapCreator(state);
    const maps = {
      isOdd: createMap(
        // 这里的s无需手动声明类型
        s => s.count,
        // 这里的count无需手动声明类型,自动推导出前面的返回数据类型
        count => count % 2 === 1
      )
    }
    
  • 不想写actions类型?

    一般插件都建议重写此类方法,以符合插件的类型提示,例如natur-immer的NaturFactory.actionsCreator

    import { NaturBaseFactory } from 'natur';
    
    const state = {
      count: 1,
    };
    
    const createMap = NaturBaseFactory.mapCreator(state);
    
    const maps = {
      isOdd: createMap(
        s => s.count,
        count => count % 2 === 1
      )
    }
    
    // 第二个参数是可选的,没有maps可以不用传
    const createActions = NaturBaseFactory.actionsCreator(state, maps);
    
    const actions = createActions({
      // 这里的api类型会自动提示,不需要再手动声明
      updateCount: (count: number) => api => {
        api.setState(count)
      }
    })
    
  • 不想写watch

    import { NaturBaseFactory } from 'natur';
    /**
     * 第一个参数是你想
     */
    const createWatch = NaturBaseFactory.watchCreator(module1, state, maps);
    const watch = createWatch({
      // event和api的类型都会自动推导出,无需手动声明
      module1Name: (event, api) => {
        // xxx
      }
    })
    

# 拦截器

在模块调用action或者store.dispatch时会先经过interceptor,因此拦截器可以应用于,控制action是否执行,以及action的入参控制等场景

import {
  createStore,
  Interceptor
  InterceptorActionRecord,
  InterceptorNext,
  InterceptorParams,
} from 'natur';

const app = {
  state: {
    name: 'tom',
  },
  actions: {
    changeName: newName => ({ name: newName }),
    asyncChangeName: newName => Promise.resolve({ name: newName }),
  },
};


type InterceptorActionRecord = {
  moduleName: String;
  actionName: String;
  actionArgs: any[];
  actionFunc: (...arg: any) => any; // 原始的action方法
}

type InterceptorNext = (record: InterceptorActionRecord) => ReturnType<Action>;

// InterceptorParams类型于MiddlewareParams类型相同
type InterceptorParams = {
  setState: MiddlewareNext, 
  getState: () => State,
  getMaps: () => InjectMaps,
  dispatch: (action, ...arg: any[]) => ReturnType<Action>,
};

const LogInterceptor: Interceptor<typeof store.type> = (interceptorParams) => 
  (next: InterceptorNext) => 
    (record: InterceptorActionRecord) => {
      console.log(`${record.moduleName}: ${record.actionName}`, record.actionArgs);
      return next(record); // 你应该return, 只有这样你在页面调用action的时候才会有返回值
};
const store = createStore(
  { app }, 
  {},
  {
    interceptors: [LogInterceptor, /* ...moreInterceptor */]
  }
);

export default store;

# 中间件

中间件的执行发生在action执行之后,更新state之前。可以接收action的返回值,一般可以应用于action返回值的加工,state更新的控制等行为

import {
  createStore,
  MiddleWare,
  MiddlewareNext,
  MiddlewareActionRecord
} from 'natur';

const app = {
  state: {
    name: 'tom',
  },
  actions: {
    changeName: newName => ({ name: newName }),
    asyncChangeName: newName => Promise.resolve({ name: newName }),
  },
};

type MiddlewareActionRecord = {
  moduleName: String,
  actionName: String,
  state: ReturnType<Action>,
}

type MiddlewareNext = (record: MiddlewareActionRecord) => ReturnType<Action>;

type middlewareParams = {
  setState: MiddlewareNext, 
  getState: () => State,
  getMaps: () => InjectMaps,
  dispatch: (action, ...arg: any[]) => ReturnType<Action>,
};

const LogMiddleware: MiddleWare<typeof store.type> = (middlewareParams) =>
  (next: MiddlewareNext) => 
    (record: MiddlewareActionRecord) => {
      console.log(`${record.moduleName}: ${record.actionName}`, record.state);
      return next(record); // 你应该return, 只有这样你在页面调用action的时候才会有返回值
      // return middlewareParams.setState(record); // 你应该return,只有这样你在页面调用action的时候才会有返回值
};
const store = createStore(
  { app }, 
  {},
  {
    middlewares: [LogMiddleware, /* ...moreMiddleware */]
  }
);

export default store;

# 内置中间件说明

# thunkMiddleware: thunk中间件可以使得action可以返回函数,拥有了获取最新的state,maps,以及setState,dispatch等增强功能

如果你喜欢mutable的写法,推荐使用natur-immer

import { thunkMiddleware, ThunkParams } from 'natur/dist/middlewares'

const actionExample = (myParams: any) => ({
  getState,
  setState,
  getMaps,
  dispatch
}: ThunkParams<typeof stateOfThisModule, typeof mapsOfThisModule>) => {
  const currentState = getState(); // 最新的state
  const currentMaps = getMaps(); // 最新的maps
  // dispatch('otherActionNameOfThisModule', ...params)
  // dispatch('otherModuleName/otherActionNameOfOtherModule', ...params);
  setState(currentState); // 更新state
  return currentState; // 更新state
}

# promiseMiddleware: action支持异步操作

// promiseMiddleware
const action1 = () => Promise.resolve(2333);
const action2 = async () => await new Promise(res => res(2333));

# fillObjectRestDataMiddleware: state增量更新/覆盖更新,state是对象时才有效

const state = {
  a: 1,
  b: 2
};
/**
 * 调用此action,最后的state是{a: 11, b:2}此中间件要求
 * state和action返回的数据必须都是普通对象
 */
const action = () => ({a: 11})

# shallowEqualMiddleware:浅层比较优化中间件,仅限于普通对象的state

const state = {
  a: 1,
  b: 2
};
const action = () => ({a: 1, b:2}) // 与旧的state相同,不做更新视图

# filterUndefinedMiddleware: 过滤返回undefined的action操作

const action = () => undefined; // 这种action的返回不会作为新的state

# devtool:开发调试工具

// redux.devtool.middleware.ts
import { Middleware } from 'natur';
import { createStore } from 'redux';

const root = (state: Object = {}, actions: any):Object => ({
  ...state,
  ...actions.state,
});

const createMiddleware = ():Middleware => {
  if (process.env.NODE_ENV === 'development' && (window as any).__REDUX_DEVTOOLS_EXTENSION__) {
    const devMiddleware = (window as any).__REDUX_DEVTOOLS_EXTENSION__();
    const store = createStore(root, devMiddleware);
    return ({getState}) => next => record => {
      store.dispatch({
        type: `${record.moduleName}/${record.actionName}`,
        state: {
          [record.moduleName]: record.state || getState(),
        },
      });
      return next(record);
    }
  }
  return () => next => record => next(record);
}

export default createMiddleware();

# 推荐的中间件配置

注意:中间件配置的先后顺序很重要


import {createStore} from 'natur';
import { 
  thunkMiddleware,
  promiseMiddleware, 
  fillObjectRestDataMiddleware,
  shallowEqualMiddleware, 
  filterUndefinedMiddleware,
} from 'natur/dist/middlewares';
import devTool from 'redux.devtool.middleware';

const store = createStore(
  modules,
  {},
  {
    middlewares: [
      thunkMiddleware, // action支持返回函数,并获取最新数据
      promiseMiddleware, // action支持异步操作
      fillObjectRestDataMiddleware, // 增量更新/覆盖更新
      shallowEqualMiddleware, // 新旧state浅层对比优化
      filterUndefinedMiddleware, // 过滤无返回值的action
      devTool, // 开发工具
    ],
  }
  
);

# typescript支持

# 基础用法

import React from 'react';
import ReactDOM from 'react-dom';
// 导入你之前创建的inject函数,详情请参考上面的简单例子
import inject from 'your-inject';
import {ModuleType, Store} from 'natur';

const count = {
  state: { // 存放数据
    number: 0,
  },
  maps: { // state的映射。比如,我需要知道state中的number是否是偶数
    isEven: ['number', number => number % 2 === 0],
  },
  actions: { // 用来修改state。返回的数据会作为新的state(这部分由natur内部完成)
    inc: number => ({number: number + 1}),
    dec: number => ({number: number - 1}),
  }
}



// 生成count模块在组件中获得的类型
type InjectCountType = ModuleType<typeof count>;

const injector = inject('count');

type otherProps = {
  className: string,
  style: Object,
}

const App: React.FC<typeof injector.type & otherProps> = (props) => {
  const {state, actions, maps} = props.count;
  return (
    <>
      <button onClick={() => actions.inc(state)}>+</button>
      <span>{state.count}</span>
      <button onClick={() => actions.dec(state)}>-</button>
    </>
  )
}

const IApp = injector(App);

const app = (
  <IApp className='1' style={{}} />
);
ReactDOM.render(
  app,
  document.querySelector('#app')
);

# 重新定义store类型

import {Store, createStore} from 'natur';

const count = {
  /* ... */
}

const lazyModule1 = () => import(/* ... */);

const allSyncModules = {
  count,
  /* and others */
}
const allAsyncModules = {
  lazyModule1,
  /* and others */
}

const store = createStore(allSyncModules, allAsyncModules);

type StoreInsType = Store<typeof allSyncModules, typeof allAsyncModules>;

// StoreInsType的类型就是store的类型,你可以扩展你的类型

# 为什么选择NATUR

# 系统设计理念

  1. natur的初衷是简单自然的掌管项目中所有的业务逻辑,在这方面是不同于redux (opens new window)或者mobx (opens new window)这样的状态管理库。natur可以轻松的让项目中所有的业务与UI层松耦合,这可以让UI保持足够的简单和纯粹,对于项目的维护性有着很大的好处。
  2. 模块的state包含了业务数据的存储。maps包含了state衍生数据的逻辑,以及缓存的设计保证性能,值得一提的是,maps使用了手动声明依赖的方式,这也与是react一贯的设计风格相符合。action包含了state数据更新,以及其他业务逻辑(比如一个没有返回值的API的调用),natur推荐action设计的职责明确,一个action只做一件事原则。如果能让每个action的执行有对应state变化,那么这能够让整个项目具有可观测和追踪性,并且可以更好的为模块间的通讯服务(可观测和追踪性可以通过拦截器中间件来实现)。
  3. 在模块通讯这里,watch可以很好的监听模块动作,并解耦模块之间依赖
  4. natur模块的设计方面,则是推荐用户细分模块,明确模块的边界,以及粒度,以保证模块的可维护性。确保一个natur模块中只处理自己的业务,而不需要关心其他模块,与别的模块没有耦合。
  5. 因为natur模块的创建和使用方式足够的简单,所以使得开发在设计方面能够尽可能简单自然的写出符合natur设计理念的项目,当然最主要的还是开发人员需要明白natur的设计理念并遵循它。

# 与redux对比

  1. 首先redux (opens new window)是全局状态管理器,这与natur的项目业务逻辑管理的设计目标是不同的。
  2. 其次redux (opens new window)的使用成本比较高,natur使用则是非常的简单
  3. 性能方面,在natur中存在缓存、模块懒加载,以及部分监听功能的支持,你不需要额外的库来保证你的项目性能。

# 与mobx的对比

  1. 兼容性方面,因为mobx (opens new window)使用了proxy或者definePropertyAPI所以兼容性要稍微差点
  2. 在模块通讯方面,mobx的设计无法完美结耦,这个是一个遗憾
  3. 性能方面,mobx的缓存性能优化依赖immutable所以在使用友好性方面较于natur更好, 但是natur也有着模块懒加载这样的功能优于mobx

# 使用注意事项

  • 由于低版本不支持react.forwardRef方法,所以不能直接使用ref获取包裹的组件实例,需要使用forwardedRef属性获取(用法同ref)

  • 在TypeScript中的提示可能不那么友好,比如

@inject('count', 'name')
class App extends React.Component {
 // ...
}

// 此使用方法会报错,提示App组件中无forwardedRef属性声明
<App forwardedRef={console.log} />

// 以下使用方式则不会报错
class _App extends React.Component {
 // ...
}
const App = inject('count', 'name')(_App);
// 正确
<App forwardedRef={console.log} />
  • 在actions中修改state,需要遵循immutable规范

# 插件

最后更新: 2023/8/2 20:53:24