npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

zk-redux

v3.0.8

Published

redux 封装

Downloads

11

Readme

redux 封装

redux 到底要解决什么问题?管理被多个组件所依赖或者影响的状态; 一个action需要被多个reducer处理吗?

关于redux

actions可以被各个页面组件和reducers复用

  • 各个页面(组件)如果挂载到路由,export出mapStateToProps,系统就会将LayoutComponent组件 或者 默认导出组件 与redux关联,即可使用this.props.actions中的方法,获取到redux中的数据;

  • 各个页面(组件)如果不是挂载到路由上的,需要显示调用connectComponent进行redux的连接

  • 各个页面(组件)如果已经与redux进行连接,通过const {actions} = this.props获取actions对象,然后调用actions.xxx() 触发action;

  • mapStateToProps 用于指定redux的state中哪部分数据用于当前组件,由于reducer的combineReducers方法包装之后,将各个reducer的state存放在对应的key中,key指的是combineReducers包装时指定的key,比如:

    zk-axios.js
    export default combineReducers({
        home, // 这个home就是key,es6写法
        utils,
    });
    
    // src/layout/home.js
    export function mapStateToProps(state) {
        return {
            ...state.home, // 这个home指的就是 combineReducers中的key
            ...state.app // 如果使用 ... home和app中如果有同名属性,app会覆盖home,可以通过调整...state.app,和...state.home得顺序,决定当前页面使用谁的属性。
        };
    }
    
  • action负责准备数据,数据来源:

    • 调用action方法时传入的参数
    • ajax请求的异步数据
    • storage/cookie中获取数据
  • reducer为纯函数,负责处理数据,要对state做deepcopy,返回一个新的数据,不要直接操作state,不会涉及异步,不操作Storage,单纯的获取action的数据之后,做进一步处理。

  • store负责将数据以pros形式传递给component,以及通过中间件对数据统一处理。

  • 组件,调用触发action,获取store处理过的数据,不发送ajax,不操作storage,单纯的展示数据。

  • 适当的区分哪些数据需要redux处理,哪些数据直接使用state,不要为了redux而redux

    • 哪些数据适合使用redux处理?
      • 异步数据(包括ajax,websocket,异步状态loading等)
      • 来自cookie/localStorage等其他存储的数据
      • 多组件公用数据
      • 多组件间通信数据
    • 哪些数据直接使用组件内部state即可?
      • 不涉及组件外数据修改(比如ajax修改后端数据),不被其他任何外部组件使用的数据,比如:点击显示隐藏modal;点击展开收起div等控制内部状态的数据。

action:

  • action 使用的是redux-actions模块构建的 Flux Standard Action
    createAction(type, payloadCreator = Identity, ?metaCreator)
  • 各个action文件之间,不允许出现同名方法,src/actions/index.js中有检测。

回调处理

调用actions方法时,给actions方法传入一个回调参数,这个回调参数,最终是由 createActionmetaCreator 参数处理的,项目中做了封装。metaCreator 可以携带业务以外的数据,异步actions会触发两次reducer,第一次触发时payloadCreator 传递给reducer的是promise对象,无法携带其他数据了,这时候就可以通过metaCreator携带额外的数据。

export const testAsync = createAction(
    types.TEXT_ASYNC_DEMO,
    async()=> {
        return await homeService.getMsg(); // 返回promise
    },
    (onResolve, onReject)=> {
    	return {
    		onResolve,
    		onReject,
    		sync: 'home'
    	}
    }
);

/* 解释

(onResolve, onReject)=> {
    return {
        onResolve,
        onReject,
        sync: 'home'
    }
}
就是 createAction 的第三个参数 metaCreator,是一个函数,这个函数返回的数据,最终存放在action的meta中。
返回数据 onResolve 和 onReject 就是调用action方法时传入的回调函数,一个代表成功,一个代表失败,这两个数据最终会被 src/store/asyncActionCallbackMiddleware.js中间件使用
*/

异步写法

异步是使用src/store/promise-middleware.js中间件进行处理的 一本异步action其实是触发了两次reducer,第一次标记异步开始,reducer可以获取相应的标记,第二次异步完成,返回数据。具体可以参考promise-middleware.js源码

action异步写法

import {createAction} from 'redux-actions';
import * as types from '../constants/actionTypes';
import * as profileService from '../services/profile-service';

export const saveUserMessage = createAction(types.SAVE_USER_MESSAGE,
    (userMessage) => profileService.saveUserMessage(userMessage), // 返回一个promise实例
    (userMessage, onResolve, onReject) => {
    // 异步action将触发reducer两次,reducer第一次触发获取payload是promise对象,额外的数据就要metaCreator提供了。
        return {
            onResolve, // 执行异步action成功回调,使页面可以获取异步成功
            onReject, // 执行异步action失败回调,使页面可以处理异步失败
            errorTip: '保存失败', // 系统自动提示错误, 默认 ‘未知系统错误’ 传递false,不使用系统提示
            successTip: '个人信息修改成功', // 默认 false,不显示成功提示信息,
        };
    }
);

reducer 异步写法:

有两种写法,第一种有机会获取所有action的数据,第二种,只能获取自己type的action数据,个人觉得获取所有action数据没有用,反而状态受干扰。推荐第二种写法

import * as types from '../constants/actionTypes';

let initialState = {
    isSidebarCollapsed: false,
    fetching: false,
};

export default function (state = initialState, action) {
    const {payload, error, meta={}, type} = action;
    const {sequence = {}} = meta;
    const status = sequence.type === 'start';
    if (status || error) { // 出错,或者正在请求中,注意: 这种写法将捕获所有异步action,自己模块得status要在自己的case中写。
        return {
            ...state,
            fetching: status,
        };
    }
    switch (type) {
    case types.TOGGLE_SIDE_BAR: {
        const isSidebarCollapsed = !state.isSidebarCollapsed;
        return {
            ...state,
            isSidebarCollapsed,
        };
    }
    case types.GET_STATE_TO_STORAGE: {
        return {
            ...state,
            ...(payload.setting || initialState),
        };
    }
    default:
        return state;
    }
}
import {handleActions} from 'redux-actions';
import * as types from '../constants/actionTypes';

let initialState = {
    loading: false,
    orderState: '',
};

export default handleActions({
    [types.SAVE_USER_MESSAGE](state, action) {
        const {error, meta = {}} = action;
        const {sequence = {}} = meta;
        const loading = sequence.type === 'start';

        // loading 要反应到页面上,
        // error由middleware处理,全局message提示,或者各个页面添加回调处理
        if (loading || error) {
            return {
                ...state,
                loading,
            };
        }

        return {
            ...state,
            orderState: 'success',
            loading,
        };
    },
}, initialState);

redux中的异常处理

  • 基于flux-standard-action 规范,异常action返回结构为:{..., payload: error, error: true, ...}
  • utils-middleware.js会统一截获处理异常(无论异步还是同步), 会根据 meta.errorTip来确定是否全局提示处理异常信息
  • async-action-callback-middleware.js 会调用actions的回调函数,给具体页面处理异常的机会

异步异常

异步操作统一使用的是promise,异常捕获在src/store/promise-middleware.js中间件中,一旦异步操作出现异常,action将传递给相应的reducer{..., payload: error, error: true, ...}

同步异常

如果action返回的payload是一个Error对象,redux-actions,将自动设置action.errortrue 自己可以在action中,使用try-catch处理???

将数据存储到localStorage中

开发过程中只需要在action中添加sync标记即可实现state存储到localStorage。 以及src/actions/utils.js 的 getStateFromStorage 方法中维护对应的sync,即可同步localStorage到state中。

action中通过 metaCreator 的 sync 属性来标记这个action相关的state是否存储到localStorage中。其中sync将会作为存储数据的key

export const setSettings = createAction(types.SET_SETTING, data => data, () => ({sync: 'setting'}));

使用 sync-reducer-to-local-storage-middleware.js 中间件进行存储操作:

import {isFSA} from 'flux-standard-action';
import * as types from '../constants/actionTypes';
import * as storage from '../utils/storage';

export default ({dispatch, getState}) => next => action => {
    if (!isFSA(action)) {
        return next(action);
    }

    const {meta = {}, sequence = {}, error, payload} = action;
    const {sync} = meta;

    if (action.type === types.SYNC_STATE_TO_STORAGE) {
        let state = getState();
        try {
            storage.setItem(payload, state[payload]);
        } catch (err) {
            /* eslint-disable */
            console.warn(err);
        }
    }

    if (!sync || sequence.type === 'start' || error) {
        return next(action);
    }

    next(action);

    setTimeout(() => {
        dispatch({
            type: types.SYNC_STATE_TO_STORAGE,
            payload: sync,
        });
    }, 16);
};

项目启动的时候,会在src/layouts/app-frame/AppFrame.jsx 中调用 actions.getStateFromStorage(); 方法,将localStorage中的数据同步到state中

actions.getStateFromStorage(); 在 src/actions/utils.js中实现:

// 同步本地数据到state中
export const getStateFromStorage = createAction(types.GET_STATE_FROM_STORAGE, () => {
    return Storage.multiGet(['setting']); // action 中使用sync标记的同时,这里也需要对应的添加,否则无法同不会来
}, (onResolve, onReject) => {
    return {
        onResolve,
        onReject,
    };
});

撤销&重做

通过 redux-undo 可以实现撤销&重做功能

通过undoable 对reducer进行包装,就可以实现撤销&重做功能

import undoable, {includeAction} from 'redux-undo';

...

export default undoable(organization, {
    filter: includeAction([types.SET_ORGANIZATION_TREE_DATA]),
    limit: 10,
    undoType: types.UNDO_ORGANIZATION,
    redoType: types.REDO_ORGANIZATION,
});

页面级别Redux写法

上述action,不仅仅将数据共享,action也进行了共享,对于页面来说如果仅仅需要共享数据,并不需要共享action,可以使用如下写法:

// 在具体的jsx页面中定义如下常量:
export const INIT_STATE = {
    scope: 'someUniqueString',
    sync: true,
    a: 3,
    b: 4,
};

说明:

  1. 如果要使用 actions.setState方法,INIT_STATE,必须定义, 必须定义,必须定义
  2. INIT_STATE:初始化state,这个会被脚本抓取,最终生成src/page-init-state.js,两个作用:
    1. 初始化放入store中的state
    2. 可以很明确看出当前页面用到的state结构
    3. scope:用来限制存放在store中的本页面state的作用于,防止页面过多,产生冲突,命名约定:各个级别模块名+文件名,驼峰命名
    4. sync: true 属性,标记当前页面state与localStorage同步,当前页面state变化将自动保存到localStorage中,页面启动时,会把localStorage中数据自动同步到redux中。
  3. 使用this.props.actions.setState(scope, payload)方法将数据存于store
  4. 如果修改当前页面数据,可以直接使用this.props.actions.setState(payload),不必指定scope
  5. 修改其他页面数据,则要指定scope,
    // 比如UserAdd.jsx页面要修改UserList.jsx页面所用到的state
    this.props.actions.setState('userListPageScope', payload);
  6. store中的state注入本组件:
    export function mapStateToProps(state) {
        return {
            ...state.pageState.someUniqueString, // someUniqueString 为当前页面scope
        };
    }
    // 通过this.props获取:
    this.props.    a;
    this.props.b
    
    // 注入多个页面state时,防止冲突,可以用如下写法
    export function mapStateToProps(state) {
        return {
            page1: state.pageState.page1Scope,
            page2: state.pageState.page2Scope,
        };
    }
    // 通过this.props获取:
    this.page1.a;
    this.page2.a;