1.7.5 • Published 5 years ago

react-natural-store v1.7.5

Weekly downloads
46
License
Apache-2.0
Repository
github
Last release
5 years ago

react-natural-store 使用手册

English doc

此包已经迁移到natur

  • 如果您的项目想从react-natural-store迁移到natur,那么可以看此迁移导航,主要是maps必须要手动声明依赖,其他的还好
  • natur是较为稳定,推荐的版本。她的兼容性,便捷性,性能都相对优异

设计概念

基本介绍

  • 这是一个简洁、高效的react状态管理器
  • 浏览器兼容:IE9+
  • 支持react 15.x, 16.x, 以及anujs
  • 单元测试覆盖率99%,放心使用
  • 包体积,minizip 5k(uglify+gzip压缩后5k)

第一步 创建 store 实例

这一步需要在渲染组件之前完成,因为 inject方法包裹的组件,在渲染时依赖store的实例

import { createStore } from 'react-natural-store';

/*
此处app视为一个模块
一个模块的数据结构就是如此
*/
const app = {
  // state 必须是Object对象,子元素不限
  state: {
    name: 'tom',
    todos: [{
      text: 'play game ',
    }],
    games: new Map(['favorite', 'lol'])
  },
  // actions必须是Object对象,子元素必须是function
  actions: {
    /*
    返回的参数
    如果是Object则会作为新的state,
    如果不是则不会处理,并原值返回给调用者。
    需要遵照immutable规范!!!
    */
    changeName: newName => ({ name: newName }),
    /*
    支持promise,
    返回值行为与上面的同步action一致
    */
    asyncChangeName: newName => Promise.resolve({ name: newName }), // 
  },
  /*
  可选的参数,必须是Object对象,子元素必须是Function 或者 Array<String | Function>
  在页面获取的maps,会是运行后的函数返回值,
  maps方法会自动收集依赖,只有在依赖发生变化时,才会重新计算结果。
  */
  maps: { 
    // 如果map仅仅是使用state第一层数据,可以直接写一个函数
    nameSplit: state => state.name.split(''), 
    addName: state => lastName => state.name + lastName,

    /** 
     * 如果你的map对性能有要求,
     * 并且依赖state的深层数据、或复杂数据,可以手动依赖声明
     * 这个示例只有在 todos[0].text 或者 s.info.get('favorite')数据发生变化时,才会重新计算结果
    */
    deepDep: [
      /*
      对于常见数据类型,你可以使用字符串路径声明依赖
      如果获取的时候发生错误,则会自动返回undefined
      */
      'todos[0].text',
      (s: State) => s.info.get('favorite'), // 对于复杂类型,你可以使用函数声明依赖
      (firstTodo, favorite) => firstTodo + favorite; // 'play game lol'
    ]
  },
};

// 其他的模块
const otherModules = { 
  //... 
};

// 创建store实例
const store = createStore({ app, ...otherModules }); 

export default store;

第二步 使用 inject 将模块注入组件当中

import { inject } from 'react-natural-store';
const App = ({app, otherModuleName}) => {
  // 获取注入的app模块
  const {state, actions, maps} = app;
  /*
    获取到的 app模块
    state: {
      name: 'tom'
    },
    actions: {
      changeName,
      asyncChangeName,
    },
    maps: {
      splitName: ['t', 'o', 'm'],
      addName: lastName => state.name + lastName,
    }
  */
  return (
    <input
      value={state.name} // app中的数据
      onChange={e => actions.changeName(e.target.value)}
    />
  )
};

// 注入store中的app模块;
export default inject('app', 'otherModuleName')(App);   

好了,你已经掌握了。以下是一些附加功能。


第二步可以换成hooks使用方式 使用 useInject 将 app 模块注入组件当中

import { useInject } from 'react-natural-store';

const App = () => {
  /*
  注意,如果useInject参数中,存在懒加载模块,则会先返回空的数组,
  等到懒加载模块加载完成才会返回你需要的模块,
  所以useInject不建议使用于懒加载模块

  但是你可以使用手动添加模块的的方式
  store.setModule('otherModuleName', otherModule);
  详情见手动导入模块说明
  */
  const [app, otherModule] = useInject('app', 'otherModuleName', /* ...moreOtherModuleName */);
  const {state, actions, maps} = app;
  return (
    <input
      value={state.name}
      onChange={e => actions.changeName(e.target.value)}
    />
  )
};
export default App; 

懒加载模块配置

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

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

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

createStore初始化state

import { createStore } from 'react-natural-store';
const app = {
  state: {
    name: 'tom',
  },
  actions: {
    changeName: newName => ({ name: newName }),
    asyncChangeName: newName => Promise.resolve({ name: newName }),
  },
};
/*
  createStore第三个参数
  {
    [moduleName: ModuleName]: Partial<State>,
  }
*/
const store = createStore(
  { app }, 
  {},
  { 
    app: {name: 'jerry'} // 初始化app 模块的state
  }
);

export default store;

中间件

import { createStore, MiddleWare, Next, Record } from 'react-natural-store';
const app = {
  state: {
    name: 'tom',
  },
  actions: {
    changeName: newName => ({ name: newName }),
    asyncChangeName: newName => Promise.resolve({ name: newName }),
  },
};
/*

抄的redux middleware,
	
type Record = {
  moduleName: String,
  actionName: String,
  state: ReturnType<Action>,
}

type Next = (record: Record) => ReturnType<Action>;

middlewareParams: {
  setState: Next, 
  getState: () => State,
};

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

export default store;

加载时候的占位组件配置

import { inject } from 'react-natural-store';
// 全局配置
inject.setLoadingComponent(() => <div>loading...</div>);

// 局部使用
inject('app')(App, () => <div>loading</div>);

在react之外使用store

// 引入之前创建的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');


// 监听模块变动
const unsubscribe = store.subscribe('app', () => {
  // 这里可以拿到最新的app数据
  store.getModule('app');
});


// 取消监听
unsubscribe();

手动导入模块

// initStore.ts
import { createStore } from 'react-natural-store';

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

// ================================================
// lazyloadPage.ts 这是一个懒加载的页面
import { useInject } from 'react-natural-store';
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}] = useInject('lazyModuleName');
  return (
    <div>{state.name}</div>
  )
}

typescript支持

import React from 'react';
import ReactDOM from 'react-dom';
import {inject, InjectStoreModule} from 'react-natural-store'

type storeProps = {count: InjectStoreModule, name: InjectStoreModule};
type otherProps = {
  className: string,
  style: Object,
}

const App: React.FC<storeProps & 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 = inject<storeProps>('count', 'name')(App);

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

使用注意事项

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

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

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

// 此使用方法会报错,提示App组件中无forwardedRef属性声明

// 以下使用方式则不会报错 class _App extends React.Component { // ... } const App = @inject('count', 'name')(_App); // 正确

- **在actions中修改state,需要遵循immutable规范**。maps只监听state下第一层值的变化,所以如果第一层值没有变化,但是state深层值确实有变化,那么对应的maps缓存不会刷新。 

- **请尽量避免在actions中动态增加或删除state的key**。这会导致一个问题,当maps依赖state动态增加或删除的key时,由于Object.defineProperty无法监听到增加key或删除key的变化,所以对应的maps不会重新计算,依然会使用上次的缓存。



#### <a id='other-version' style="color: black;">其他版本</a>

- [纯净版](https://www.npmjs.com/package/rns-pure)
- [taro版本](https://www.npmjs.com/package/rns-taro)
1.7.5

5 years ago

1.7.3

5 years ago

1.7.4

5 years ago

1.7.2

5 years ago

1.7.1

5 years ago

1.7.0

5 years ago

1.6.9

5 years ago

1.6.8

5 years ago

1.6.7

5 years ago

1.5.6

5 years ago

1.5.5

5 years ago

1.5.4

5 years ago

1.4.4

5 years ago

1.3.4

5 years ago

1.2.4

5 years ago

1.2.3

5 years ago

1.2.2

5 years ago

1.1.2

5 years ago

1.1.2-beta6

5 years ago

1.1.2-beta5

5 years ago

1.1.2-beta4

5 years ago

1.1.2-beta3

5 years ago

1.1.2-beta2

5 years ago

1.1.2-beta1

5 years ago

0.1.2

5 years ago

0.1.1

5 years ago

0.1.0

5 years ago

0.0.9

5 years ago

0.0.9-beta.8

5 years ago

0.0.9-beta.7

5 years ago

0.0.9-beta.6

5 years ago

0.0.9-beta.5

5 years ago

0.0.9-beta.4

5 years ago

0.0.9-beta.3

5 years ago

0.0.9-beta.2

5 years ago

0.0.9-beta.1

5 years ago

0.0.8

5 years ago

0.0.7

5 years ago

0.0.6

5 years ago

0.0.5

5 years ago

0.0.4

5 years ago

0.0.3

5 years ago

0.0.2

5 years ago

0.0.1

5 years ago