文章目录

    • 基础
      • 什么是Redux?
      • 安装Redux
      • 核心思想
      • 三大原则
        • 单一数据源
        • State只读
        • 使用纯函数来执行修改
      • Action
      • Reducer
      • Store
      • State的基本结构
      • `React-Redux` 使用
        • 安装React Redux
        • 核心API讲解
          • 1. Provider
          • 2. connect
        • 完整示例代码
        • 扩展:
          • 1. 嵌套组件中访问Redux Store State
          • 2. 使用`combineReducers`合并多个零散Reducer
          • 3. 使用bindActionCreators简化Action的分发
    • 高级
      • 异步Action
        • 什么是(为什么使用)Redux Thunk?
        • Redux Thunk在异步Action中使用
      • 范式化数据
        • 为什么要设置范式化state数据结构?
        • 设计范式化State数据结构
        • 表间关系
        • 嵌套数据范式化
        • 管理范式化数据
      • 中间件使用
        • redux-thunk
        • redux-saga
        • redux-ignore
        • redux-router
        • redux-promise
        • reselect
          • 为什么需要reselect?
          • 什么是reselect?
      • 参考文献

基础

什么是Redux?

Redux是JavaScript状态容器,提供可预测化的状态管理。可以让你构建一致化的应用,运行于不同的环境。
在这里插入图片描述

安装Redux

npm install --save redux
#或者
yarn add redux

核心思想

Redux核心思想是通过action来更新state。

Action就像是描述发生了什么的指示器。最终为了把action和state串起来,开发一些函数,这些函数叫做reducer。reducer只是一个接收state和action并返回新的state的函数。

对于大应用来说,不大可能仅仅只写一个这样的函数,所以我们编写很多小函数来分别管理state的一部分,最后通过一个大的函数调用这些小函数,进而管理整个应用的state。

三大原则

单一数据源

整个应用的state被存储在一棵object tree中,并且这个object tree只存在于唯一一个store中。

console.log(store.getState())
/* 输出
{visibilityFilter: 'SHOW_ALL',todos: [{text: 'Consider using Redux',completed: true,},{text: 'Keep all state in a single tree',completed: false}]
}
*/

State只读

唯一改变state的方法就是触发action,action是一个用于描述已发生事件的普通对象。

这样确保了视图和网络请求都不能直接修改state,相反它们只能表达想要修改的意图。因为所有的修改都被集中化处理,且严格按照一个接一个顺序执行,因此不用担心竞态条件的出现。

Action就是普通对象而已,因此它们可以被日志打印、序列化、存储、后期调试或测试回放出来。

//定义Action对象,并通过store.dispatch方法触发Action
store.dispatch({type:'COMPLETE_TODO',index:1
})store.dispatch({type:'SET_VISIBILITY_FILTER',filter:'SHOW_COMPLETED'
})

使用纯函数来执行修改

为了描述action如何改变 state tree,你需要编写reducers函数

Reducer只是一些纯函数,它接收先前的state和action,并返回新的state。

刚开始你可能只有一个reducer,随着应用的变大,你可以把它拆成多个小的reducers,分别独立的操作state tree的不同部分,因为reducers只是函数,你可以控制它们被调用的顺序,传入附加数据,甚至编写可复用的reducer函数来处理一些通用任务。

//1. 导入redux中的combineReducers、createStore对象
import {combineReducers,createStore} from 'redux';//2. 定义多个小的reducer函数处理特定的state(参数为先前的state和action)
function visibilityFilte(state = 'SHOW_ALL' , action){switch(action.type){case 'SET_VISIBBILITY_FILTER':return action.filterdefault:return state}}function todos(state = [] , action){switch(action.type){case 'ADD_TODO':return [...state,{text:action.text,completed:false,}]case 'COMPLETED_TODO':return state.map((todo,index)=>{if(index === action.index){return Object.assign({},todo,{completed:true})}return todo})default:return state     }
}//3. 通过combineReducers函数将多个小的reducers函数组合。
let reducer = combineReducers({visibilityFilte,todos})//4. 通过reducer函数创建Redux Store对象来存放应用状态
let store = createStore(reducer);//5. 可以手动订阅更新,也可以事件绑定到视图层
store.subscribe(()=>{//当state更新会触发这里console.log(store.getState());
});//6. 通过store指定action来触发Action改变state,
store.dispatch({type:'COMPLETE_TODO',index:1
})store.dispatch({type:'SET_VISIBILITY_FILTER',filter:'SHOW_COMPLETED'
})

Action

  • 简介

    Action 是把数据从应用(这里之所以不叫View是因为这些数据有可能是从服务器响应,用户输入或其他非View的数据)传到store的有效载荷。它是store数据的唯一来源。一般来说你会通过store.dispatch()将action传到store。(简单说:action用于描述发生了什么)

    添加新的todo任务的action是这样的:

    const ADD_TODO = 'ADD_TODO'{type:ADD_TODO,text:'Build my first Redux app'
    }
    

    Action本质是JavaScript的普通对象。我们约定,action内必须使用一个字符串类型的type字段来表示将要执行的动作。多数情况下,type会被定义成字符串常量。当应用规模越来越大时,建议使用独立的模块或文件存放action。

    import {ADD_TODO,REMOVE_TODO} from '../actionTypes'
    

    除了type字段外,action对象的结构完全由你自己决定,参照 Flux 标准 Action 获取关于如何构造 action 的建议。

    这时,我们还需要再添加一个action index来表示用户完成任务的动作序列号。因为数据存放在数组中的,所以我们通过下标Index来引用特定的任务。而实际项目中一般会在新建数据的时候生成唯一的ID作为数据的引用标识。

    {type:TODO_ADD,index:5
    }
    

    我们应该尽量减少在action中传递数据。比如上面的例子,传递index就比把整个任务对象传过去要好

  • Action创建函数(Action Creator)

    Action创建函数就是生成action的方法。actionaction创建函数这两个概念很容易混在一起,使用时最好注意区分。

    在Redux中的action创建函数只是简单的返回一个action:

    function addTOdo(text){return {type:ADD_TODO,text}
    }
    

    这样做将使得action创建的函数更容易被移植和测试。

    store里能直接通过store.dispatch()调用dispatch()方法,但是多数情况下你会使用react-redux提供的connect()帮助器来调用。bindActionCreators()可以自动把多个action创建的函数绑定到dispatch()方法上。

    注意:我们通常使用此中方式(action的工厂函数/action creator)构造action对象)

  • 源码案例actions.js

    //action 类型(大型项目一般会独立在一个组件中声明,然用导出供其他组件使用)
    export const ADD_TODO = 'ADD_TODO';
    export const TOGGLE_TODO = 'TOGGLE_TODO';
    export const SET_VISIBILITY_FILTER = 'SET_VISIBILITY_FILTER';//其他常量对象
    export const VisibilityFilters = {SHOW_ALL:'SHOW_ALL',SHOW_COMPLETED:'SHOW_COMPLETED',SHOW_ACTIVE:'SHOW_ACTIVE'
    }//创建action函数并导出,返回action
    export function addTodo(text){return {type:ADD_TODO,text}
    }export function toggleTodo(index){return {type:TOGGLE_TODO,index}
    }export function setVisibilityFilter(filter){return {type:SET_VISIBILITY_FILTER,filter}
    }
    

Reducer

  • 简介

    Reducers指定了应用状态如何响应actions并发送到store的,记住actions只是描述了有事情发生这一事实,并没有描述应用如何更新state。(简单说:reducer根据action更新state)

    整个应用只有一个单一的 reducer 函数:这个函数是传给 createStore 的第一个参数。一个单一的 reducer 最终需要做以下几件事:

    • reducer 第一次被调用的时候,state 的值是 undefined。reducer 需要在 action 传入之前提供一个默认的 state 来处理这种情况。
    • reducer 需要先前的 state 和 dispatch 的 action 来决定需要做什么事。
    • 假设需要更改数据,应该用更新后的数据创建新的对象或数组并返回它们。
    • 如果没有什么更改,应该返回当前存在的 state 本身。

    注意:保持reducer纯净非常重要,永远不要在reducer里做这些操作

    • 修改传入参数
    • 执行有副作用的操作,例如:请求和路由跳转;
    • 调用非纯净函数,如:Date.now()Math.random()

    只需要谨记reducer一定要保持纯净。只要传入参数相同,返回计算得到的下一个state就一定相同。没有特殊情况、没有副作用、没有API请求、没有变量修改,单纯执行计算。

  • Action处理

    Redux首次执行时,state为undefined,此时我们可借机设置并返回应用的初始state。

    //引入 VisibilityFilters 常量对象
    import {VisibilityFilters} from './actions'//初始化state状态。
    const initialState={visibilityFilter:VisibilityFilters.SHOW_ALL,todos:[]
    };//定义reducer函数
    function todoApp(state,action){//如果state为定义返回初始化的stateif(typeof state === 'undefined'){return initialState;}//这里暂不处理任何action,仅返回传入的statereturn state
    }
    

    使用ES6参数默认值语法精简代码

    function todoApp(state = initialState,action){//这里暂不处理任何action,仅返回传入的statereturn state;
    }
    

    现在可以处理action.type SET_VISIBILITY_FILTER。需做的只是改变state中的visibilityFilter:

    function todoApp(state = initialState,action){switch(action.type){case 'SET_VISIBILITY_FILTER':return Object.assign({},state,{visibilityFilter:action.filter})default:return state;}
    }
    

    注意:

    1. 不要修改state

      使用Object.assign() 新建了一个副本。不要使用下面方式

      Object.assign(state,{visibilityFilter:action.filter}),因为它会改变第一个参数的值。你必须把第一个参数设置为空对象。

    2. default情况下返回旧的state。遇到未知的action时,一定要返回旧的state

      Object.assign介绍

      Object.assign(target,source1,source2...sourceN)是ES6特性,用于对象的合并。

      //对象合并:source1,source2合并到target中。
      const target = { a: 1 };
      const source1 = { b: 2 };
      const source2 = { c: 3 };
      Object.assign(target, source1, source2);
      target // {a:1, b:2, c:3}//同名属性的替换:source中a属性值替换掉target中的a属性值。
      const target = { a: { b: 'c', d: 'e' } }
      const source = { a: { b: 'hello' } }
      Object.assign(target, source)// { a: { b: 'hello' } }//数组的处理:assign会把数组视为属性名为 0,1,2 的对象,
      //因此源数组的0号属性值4覆盖了目标数组的0号属性值1。
      Object.assign([1, 2, 3], [4, 5])// [4, 5, 3]

      注意:

      • 该方法的第一个参数是目标对象,后面的参数都是源对象;
      • 如果目标对象与源对象或多个源对象有同名属性,则后面的属性会覆盖前面的属性;
      • Object.assign是浅拷贝。如果源对象某个属性的值是对象,那么目标对象拷贝得到的是这个对象的引用,这个对象的任何变化,都会反映到目标对象上面;
      • 同名属性的替换。对于嵌套的对象,一旦遇到同名属性,Object.assign的处理方法是替换,而不是添加。
  • 处理多个Action

    注意

    • 每个reducer只负责管理全局state中它负责的一部分。每个reducer的state参数都不同,分别对应它管理的那部分state数据。

    最后。Redux提供了**combineReducers()**工作类,用于生成一个函数,这个函数来调用你的一系列的reducer,每个reducer根据它们的key来筛选出state中的一部分数据并处理,然后这个生成的函数再将所有的reducer的结果合并成一个大的对象。

  • 代码案例reducers.js

    import {combineReducers} from 'redux'
    //从actions文件中中导入action type常量和函数
    import {ADD_TODO,TOGGLE_TODO,SET_VISIBILITY_FILTER,VisibilityFilters
    } from './actions'//解构赋值
    const {SHOW_ALL} = VisibilityFilters//定义显示筛选的reducer函数
    function visibilityFilter(state = SHOW_ALL,action){switch(action.type){case SET_VISIBILITY_FILTER:return action.filterdefault:return state   }
    }//定义处理事物的reducer函数
    function todos(state = [] , action){switch(action.type){case ADD_TODO:return [...state,{text:action.text,completed:false } ]case TOGGLE_TODO:return state.map((todo,index)=>{if(index ==== action.index){return Object.assign({},todo,{completed:!todo.completed})}return todo})default:return state   }
    }//通过combineReducer函数将自定义的多个reducers关联起来
    const todoApp = combineReducers(){visibilityFilter,todos
    }//导出该Reducer供外界使用
    export default todoApp;
    

Store

  • 简介

    前面介绍了action来描述“发生了什么”,使用reducers来根据action更新state的用法。

    Store 就是把action和reducer联系到一起的对象。Store有以下职责:

    • 维持应用的state;
    • 提供getState()方法获取state;
    • 提供dispatch(action)方法更新state;
    • 通过subscribe(listener)注册监听器;
    • 通过subscribe(listener)返回的函数unsubscribe用于注销监听器。

    再次强调一下Redux应该只有一个单一的store。当要拆分数据处理逻辑时,你应该使用reducer组合而不是创建多个store。

  • createStore创建Store

    根据已有的reducer来创建store是非常容易的,前一节中我们通过combineReducers()将多个reducer合并为一个。现我们将其导入,通过其使用createStore(reducers)创建Store:

    import {createStore} from 'redux'
    //导入定义好的reducers对象
    import todoApp from './reducers'
    //创建Store对象
    let store = createStore(todoApp)
    

    createStore()的第二个参数是可选的,用于设置state的初始状态。这对开发同构应用时非常有用,服务器端redux应用的state结构可以于客户端保持一致,那么客户端可以将从网络接收到的服务端state直接用于本地数据初始化:

    let store createStore(todoApp,window.STATE_FROM_SERVER)
    
  • 发起Actionindex.js

    经过上述几个步骤,我们已经创建了actionreducerstore了,此时我们可以验证一下,虽然没有页面,因为它们都是纯函数,只需要调用一下,对返回值做判断即可。写测试就这么简单。

    import {createStore} from 'redux'//1. 引入定义好的action
    import {addTodo,toggleTodo,setVisibilityFilter,VisibilityFilters} from './actions'//2. 引入定义好的reducer
    import todoApp from './reduces'//3. 创建store
    let store = createStore(todoApp)//4. 触发action通过reducer更新state
    store.dispatch(addTodo('Learn about actions'))
    store.dispatch(addTodo('Learn about reducers'))
    store.dispatch(addTodo('Learn about store'))
    store.dispatch(toggleTodo(0))
    store.dispatch(toggleTodo(1))
    store.dispatch(setVisibilityFilter(VisibilityFilter.SHOW_COMPLETED))//5. 通过subscribe开启监听state更新,注意返回一个函数对象用于注销监听
    const unsubscribe = store.subscribe(()=>{console.log(store.getState())
    })//6. 停止监听state更新
    unsubscribe();

State的基本结构

Redux 鼓励你根据需要管理的数据来思考你的应用程序。数据就是你的应用state。

Redux state中顶层的状态树通常是一个普通的JavaScript对象(当然也可以是其他类型的数据,比如:数字、数据或者其他专门的数据结构,但大多数库的顶层值都是一个普通对象)。

大多数应用会处理多种数据类型,通常可以分为以下三类:

  • 域数据(Domain data):应用需要展示、使用或者修改的数据;
  • 应用状态(App state):特定与应用某个行为的数据;
  • UI状态(UI state):控制UI如何展示的数据。

一个典型的应用state大致会长这样:

{domainData1:{},   //数据域1domainData2:{},   //数据域2appState1:{},     //应用状态域1appState2:{},     //应用状态域1ui:{              //UI域uiState1:{},uiState2:{},} 
}

React-Redux 使用

这里强调一下Redux和React之间没有任何关系。Redux支持React、Angular、Ember、JQuery、甚至纯JavaScript。

尽管此次,Redux还是和React和Deku这类库搭配使用最好,因为这类库允许你以state函数的形式来描述界面,Redux通过action的形式来发起state变化。

安装React Redux

Redux默认并不包含React绑定库,需要单独安装。

npm install --save react-redux
#或者
yarn add react-redux

核心API讲解

1. Provider

provider组件是react-redux提供的核心组件,作用是将Redux Store提供可供内部组件访问。

使用

通过react-redux提供的Provider组件包括其他组件

//导入Provider组件
import {Provider} from 'react-redux';
//导入创建好的Redux Store对象
import store from './store';
//导入组件
import CustomComponent from './CustomComponent';export default class App extends Component{render(){return(<Provider><CustomComponent/></Provider>);} 
}
2. connect

connect组件也是react-redux提供的核心组件,作用是将当前组件与Redux Store进行关联,以便通过mapStateToProps、mapDispatchToProp函数将当前组件Props与Redux Store 中的State和dispatch建立映射。

注意:

  1. 使用 connect() 前,需要先定义 mapStateToProps;
  2. 使用connect连接的组件需要被Provider组件包裹;
  3. mapStateToProps:这个函数来指定将当前组件Props与 Redux store state 建立映射关系。在每次 store 的 state 发生变化的时候,应用内所有组件的该函数都会被调用。如果不传组件不会监听Store State的变化,也就是说Store的更新不会引起UI的更新。
  4. mapDispatchToProps:这个函数用来指定将当前组件Props与store.dispatch建立映射关系。如果不传React-Redux会自动将dispatch注入组件的props(可通过this.props.dispatch(action)使用)。

使用

在Provider组件包裹的组件通过react-redux提供的connect将组件与Redux Store进行连接。

//1. 引入 connect
import { connect } from 'react-redux';
class CustomComponent extends Component{render(){return (<View style={{flex: 1, flexDirection: 'column', alignItems: 'center'}}><Button title={'修改显示数据'} /***通过this.props.btnOnClick指定函数来间接触发store.dispatch(action)*更改Store中State。*/onPress={() => {this.props.btnOnClick();}}/>//通过属性映射this.props.showText获取到Redux Store State中的数据。<Text style={{marginTop: 20}}>{this.props.showText}</Text></View>);}
}/***2.创建mapStateToProp函数*/
const mapStateToProps = (state) => {return {/***将Redux Store State中的loginText映射到当前组件的showText属性上,*然后当前组件通过this.props.showText即可获取存储在Store State中的loginText对应的值。*/showText: state.loginText, //将Redux Store State中的loginStatus状态映射到当前组件的showStatus属性上。showStatus: state.loginStatus,}
};/***3.创建mapDispatchToProp函数*/
const mapDispatchToProps = (dispatch)=>{return{/***changeShowTex:为自定义的Props属性函数名,当前组件通过this.props.changeShowText()*即可触发store.dispatch(action)来更新Redux Store State中数据。*/changeShowText:()=>{dispatch(action);   //发送指定的action来更改Store中的State}}
}/***4.通过connect函数将当前组件与Redux的Store连接起来。(当前组件需要被Provider组件包裹的)*/
export default connect(mapStateToProps,mapDisPatchToProps)(CustomComponent);

完整示例代码

实现目标:通过点击页面按钮(触发store.dispatch(action)),来更改当前显示的文案信息(这里的文案显示信息存储在Redux Store State中)

  1. 定义Action 类型

    //ActionType.js
    //登陆状态
    const LOGIN_TYPE = {LOGIN_SUCCESS: 'LOGIN_SUCCESS',LOGIN_FAILED: 'LOGIN_FAILED',LOGIN_WAITING: 'LOGIN_WAITING',
    };
    export {LOGIN_TYPE};
    
  2. 创建Action(告诉Reducer要做什么操作)

    //Actions.js
    //导入action types
    import {LOGIN_TYPE} from '../ActionType';export const loginWaiting = {type: LOGIN_TYPE.LOGIN_WAITING,text: '登陆中...',
    };export const loginSuccess = {type: LOGIN_TYPE.LOGIN_SUCCESS,text: '登陆成功...',
    };export const loginFailed = {type: LOGIN_TYPE.LOGIN_FAILED,text: '登陆失败...',
    };
    
  3. 创建Reducer(根据传递进来的action type来处理相应逻辑返回新的state)

    //Reducer.js
    //导入action type
    import {LOGIN_TYPE} from '../ActionType';//默认的state
    const defaultState = {loginText: '内容显示区',loginStatus: 0,
    };/***创建reducer:*根据当前action类型更改Store State中的loginText和loginStatus,*会回调发送store.dispatch(action)事件组件的mapStateToProps函数。*/
    const AppReducer = (state = defaultState, action) => {switch (action.type) {case LOGIN_TYPE.LOGIN_WAITING:    return {loginText: action.text,loginStatus: 0,};case LOGIN_TYPE.LOGIN_SUCCESS:return {loginText: action.text,loginStatus: 1,};case LOGIN_TYPE.LOGIN_FAILED:return {loginText: action.text,loginStatus: 2,};default:return state;}
    };
    export {AppReducer};
    
  4. 创建Redux Store

    //store.js
    import {createStore} from "redux";
    import {AppReducer} from '../reducers/AppReducer';
    //依据Reducer创建store
    const store = createStore(AppReducer);
    export default store;
    
  5. 使用入口

    //App.js
    //通过react-redux提供的Provider组件将store传递给子组件访问
    import React,{Component} from 'react';
    import {Provider} from 'react-redux';
    import store from './store';
    import CustomComponent from '../page/CustomComponent';
    import CustomComponent2 from '../page/CustomComponent2';
    import CustomComponent3 from '../page/CustomComponent3';
    export default class App extends Component{render(){return(<Provider store={store}><CustomComponent/>    //子组件<CustomComponent2/>   //子组件2<CustomComponent3/>   //子组件3...</Provider>)} 
    }
    

    上面我们通过react-redux提供的Provider组件将我们创建好的store提供给子组件CustomComponent访问,接下来我们看看子组件中如何与Redux Store建立关系,并访问其State中内容。

  6. 子组件中访问Redux Store State数据(以CustomComponent为案例,其他子组件一样)

    //CustomComponent.js
    import React,{Component} from 'react';
    import {View,Text,Button} from 'react-native';//导入Action,下面业务点击要触发dispatch(action)
    import * as Actions from '../actions/Actions';//1.导入connect,下面需要将当前组件与Redux Store通过该connect建立连接。
    import {connect} from 'react-redux';export default class CustomComponent extends Component{render(){return(<View style={{flex: 1, flexDirection: 'column', alignItems: 'center'}}>/***5.当前组件通过this.props.xxx 指定mapStateToProps函数中自定义的属性来获取*	 从Redux Store State映射的值。*/<Text style={{marginTop: 20}}>{this.props.showText}</Text><Button title={'模拟登陆中'} onPress={() => {/***6.当前组件通过this.props.xxx() 调用mapDispatchToProps自定义的函数,*以此间接触发store.dispatch(action)来发送action达到更新Store中State目的*///当connect第二个参数不传递的时候,Redux Store会自动将dispatch映射到Props上。//this.props.dispatch(Actions.loginWaiting);} this.props.btnOnClick(1);}}/><Button title={'模拟登陆成功'} onPress={() => {this.props.btnOnClick(1);}}/><Button title={'模拟登陆失败'} onPress={() => {this.props.btnOnClick(2);}}/></View>)}
    }/***2.定义mapStateToProps函数(当Store中的State变化时候,会回调改函数)*	返回一个Object,内部是将state中的值映射到自定义的属性上,以便当前组件通过this.props.xxx来*	获取State中数据。*/
    const mapStateToProps = (state)=>{return{//将Redux Store State中的loginText映射到自定义的showText属性上。showText:state.loginText,//将Redux Store State中的loginStatus映射到自定义的showStatus属性上。showStatus:state.loginStatus,}
    }/***3.定义mapDispatchToProps函数:*	返回一个Object,内部定义的属性函数名称,以便当前组件通过调用this.props.xxx()*	来间接触发store.dispatch(action)。*/
    const mapDispatchToProps = (dispatch)=>{return {changeShowText:(type)=>{switch(type){case 0:dispatch(Actions.loginWaiting);break;case 1:dispatch(Actions.loginSuccess);break;case 2:dispatch(Actions.loginFailed);break;   }}}
    }/***4.通过connect将当前CustomComponent组件与Redux Store建立连接,并通过mapStateToProps、*	mapDispatchToProps函数将Redux Store State映射到当前组件Props中。*/
    export default connect(mapStateToProps,mapDispatchToProps)(CustomComponent);
    

扩展:

1. 嵌套组件中访问Redux Store State

如下组件:

根组件APP.js

return(<Provider><CustomComponent/></Provider>
)

子组件CustomComponent.js

//内部引入Child组件
return(...<Child/>
)

我们在Child组件中如果要访问Redux Store State与CustomComponent组件访问方式一样,如下:

Child.js

import React,{Component} from 'react';
import {View, Text, Button} from 'react-native';
//1.导入connect
import {connect} from 'react-redux';
import * as Actions from '../actions/CommonAction';
export default class Child extends Component{render(){return(<View>/***使用:通过this.props.xxxx 指定mapStateToProps定义的属性名*获取Store State映射的数据。*/<Text>{this.props.xxxx}</Text><Button onPress={()=>{/***通过this.props.xxxx()调用mapStateToProps声明的函数*间接触发store.dispatch(action)来更新Redux Store State。*/this.props.xxx();}}></View>);}//2.定义mapStateToProps函数
const mapStateToProps = (state)=>{return{//TODO...}
}//3.定义mapDispatchToProps函数
const mapStateToProps = (dispatch)=>{return{//TODO...}
}/***4.通过connect将当前组件与Redux Store建立连接,并通过mapStateToProps、mapStateToProps函数*将Store 的 State和dispatch映射到Props中*/
export default connect(mapStateToProps,mapStateToProps)(Child);}
2. 使用combineReducers合并多个零散Reducer

上面的代码中我们的Action以及Reducer都定义在一个文件中,对于中大型项目后期错误的排查和维护比较困难,因此我们重构项目,将Action和Reducer依据业务功能拆分使其各自独立,通过借助combineReducers对多个Reduce进行合并。

比如我们有登陆、注册页面,因此我们将原来的Action拆分成LoginAction、RegisterAction;将原来的Reducer拆分成LoginReducer、RegisterReducer使其各司其职处理相关的业务。

  1. 拆分Action

    LoginAction.js

    /***登陆Action*PS:目前action触发携带的是静态数据,内部的data都是写好的,*后面会扩展通过接口请求返回数据填充到data中*/
    import {LOGIN_TYPE} from './ActionType';
    export const loginWaiting = {type: LOGIN_TYPE.LOGIN_WAITING,data: {status: 10,text: '登陆中...',},
    };
    export const loginSuccess = {type: LOGIN_TYPE.LOGIN_SUCCESS,data: {status: 11,text: '登陆成功!',},
    };
    export const loginFailed = {type: LOGIN_TYPE.LOGIN_FAILED,data: {status: 12,text: '登陆失败!',},
    };
    

    RegisterAction.js

    /***注册Action*/
    import {REGISTER_TYPE} from './ActionType';
    export const registerWaiting = {type: REGISTER_TYPE.REGISTER_WAITING,data: {status: 20,text: '注册中...',},
    };
    export const registerSuccess = {type: REGISTER_TYPE.REGISTER_SUCCESS,data: {status: 21,text: '注册成功!',},
    };
    export const registerFailed = {type: REGISTER_TYPE.REGISTER_FAILED,data: {status: 22,text: '注册失败!',},
    };
    
  2. 拆分Reducer

    LoginReducer.js

    //默认登陆页面属性
    const defaultLoginState = {Ui: {loginStatus: '',   //登陆状态(用于控制登陆按钮是否可点击、以及显示加载框等)loginText: '',     //登陆不同状态下的提示的文字},
    };
    const LoginReducer = (state = defaultLoginState, action) => {switch (action.type) {case LOGIN_TYPE.LOGIN_WAITING:return {...state,Ui: {loginStatus: action.data.status,loginText: action.data.text,},};case LOGIN_TYPE.LOGIN_SUCCESS:return {...state,Ui: {loginStatus: action.data.status,loginText: action.data.text,},};case LOGIN_TYPE.LOGIN_FAILED:return {...state,Ui: {loginStatus: action.data.status,loginText: action.data.text,},};default:return state;}
    };
    export default LoginReducer;
    

    RegisterReducer.js

     //注册页面默认状态
    const defaultRegisterState = {Ui: {registerStatus: '',   //登陆状态(用于控制登陆按钮是否可点击、以及显示加载框等)registerText: '',     //登陆不同状态下的提示的文字},
    };
    const RegisterReducer = (state = defaultRegisterState, action) => {switch (action.type) {case REGISTER_TYPE.REGISTER_WAITING:return {...state,Ui: {registerStatus: action.data.status,registerText: action.data.text,},};case REGISTER_TYPE.REGISTER_SUCCESS:return {...state,Ui: {registerStatus: action.data.status,registerText: action.data.text,},};case REGISTER_TYPE.REGISTER_FAILED:return {...state,Ui: {registerStatus: action.data.status,registerText: action.data.text,},};default:return state;}
    };
    export default RegisterReducer;
    

    通过combineReducer({key1:reducer1,key2:reducer2})将reducer组合

    注意:

    • 使用combineReducers进行组合Reducer时候我们可指定Reducer名称key,也可省略(默认使用Reducer导出的组件名)。
    • 通过combineReducers组合后,在展示组件中通过mapStateToProps函数映射时候我们需要指定combineReducers合并时指定的Reducer名称来访问Redux Store State中的数据(见下面案例)
    //合并Reducer
    import LoginReducer from './LoginReducer';
    import RegisterReducer from './RegisterReducer';
    const AppReducers = combineReducers({LoginReducer,                   //没有指定LoginReducer名称,Redux默认使用LoginReducerregisterReducer:RegisterReducer,//指定LoginReducer名称为registerReducer,
    });
    export default AppReducers;
    

    后面的Reducer使用不变,通过指定AppReducers使用createStore来创建Store。

    重构以后运行看一下我们的State中的数据格式如下:

    /***通过combineReducers组合后的Reducer,在Redux Store State中会*自动为不同的Reducer添加名称区分各自数据状态区*/
    {"LoginReducer": {"Ui": {"loginStatus": 10,"loginText": "登陆中..."}},"registerReducer": {"Ui": {"registerStatus": "","registerText": ""}}
    }
    

    接下来我们在展示组件的mapStateToProps中将Redux Store State映射到展示组件的Props中

    登陆页面/组件(Login.js)

    //定义connect函数第一个参数:将Store中的state映射到当前组件的props上
    const mapStateToProps = (state) => {return {/***这里我们通过state.xxx方式将问Redux Store State中属性映射到当前组件Props属性上。*其中[xxx] 为combineReducers合并Reducer时指定的名称(没指定默认使用组件导出名)*/loginShowText: state.LoginReducer.Ui.loginText,loginShowStatus: state.LoginReducer.Ui.loginStatus,};
    };
    

    注册页面/组件(Register.js)

    //定义connect函数第一个参数:将Store中的state映射到当前组件的props上
    const mapStateToProps = (state) => {return {/***通过state.xxxx 指定 combineReducers 合并Reducer时指定的名称,*将Redux Store State属性映射到展示组件的属性上。*/registerShowText: state.registerReducer.Ui.registerText,registerShowStatus: state.registerReducer.Ui.registerStatus,};
    };
    

    重构后的项目结构如下,这样我们就可以根据业务模块进行针对性的处理Action和Reducer内业务逻辑,使其逻辑更清晰,提高项目的可读性和维护性。 在这里插入图片描述

3. 使用bindActionCreators简化Action的分发

什么是bindActionCreators?

bindActionCreators作用是在使用redux的connect将react与redux store关联起来的connectmapDispatchToProps函数中将单个或多个Action Creator转化为dispatch(action)的函数集合形式。开发者不用再手动dispatch(actionCreator(type)),而是可以直接调用方法。

bindActionCreators原理

bindActionCreators实际上就是将dispatch直接和单个或多个action creator结合好然后发出去的这一部分操作给封装成一个函数。bindActionCreators 会使用dispatch将这个函数发送出去。

使用与不使用bindActionCreators对比

假如我们通过action creator来创建action

UserAction.js

//添加用户的同步action
export const addUser = (user) => {return {type: ADD_USER,user,};
};
//删除用户的同步action
export const removeUser = (user)=>{return {type: REMOVE_USER,user,};
}
//计算总用户数量
export const sumUser = () => {return {type: SUM_USER,};
};

然后在TestComponent.js组件中通过mapDispatchToProps函数中使用:

  • 不使用bindActionCreators

    //1.导入UserAction
    import {addUser,removeUser,sumUser} from './actions/UserAction';//2.定义connect的mapDispatchToProps函数
    const mapDispatchToProps = (dispatch)=>{return{propsAddUser:(user)=>{//通过dispatch来分发指定的Actiondispatch(addUser(user))},propsRemoveUser:(user)=>{dispatch(removeUser(user))},propsSumUser:()=>{dispatch(sumUser())}}
    }
    //通过connect将react与Redux Store关联并导出组件
    export default connect(mapStateToProps,mapDispatchToProps)(TestComponent)//3.组件内调用mapDispatchToProps中定义的映射的props
    <Button title='添加用户' onpress={()=>{let user={name:'zcmain',age:20,address:'中国上海'}//通过this.props.xxx 指定调用mapDispatchToProps中定义的属性即可。this.props.propsAaddUser(user);//this.props.propsRemoveUser(user);//this.props.propsSumUser();
    }}>
    
  • 使用bindActoinCreators

    格式:

    bindActionCreators(actionCreators,dispatch)

    参数:

    • actionCreators:(函数对象):也可以是一个对象,这个对象的所有元素都是action create函数。
    • dispatch:(功能):在Store实例dispatch上可用的功能。

    示例:

    //1.导入UserAction
    import {addUser,removeUser,sumUser} from './actions/UserAction';//2.导入redux中的bindActionCreators
    import {bindActionCreators} from 'redux';//3.定义connect的mapDispatchToProps函数
    const mapDispatchToProps = (dispatch)=>{return{/***使用bindActionCreators将多个action creator转换成dispatch(action)形式,*此处不在手动调用disptch(action)了。*/actions:bindActionCreators({propsAddUser:(user)=>addUser(user),propsRemoveUser:(user)=>removeUser(user),propsSumUser:sumUser,  //不带参数的action creator},dispatch),}
    }
    //通过connect将react与Redux Store关联并导出组件
    export default connect(mapStateToProps,mapDispatchToProps)(TestComponent)//4.组件内调用mapDispatchToProps中定义的映射的props
    <Button title='添加用户' onpress={()=>{let user={name:'zcmain',age:20,address:'中国上海'}/***通过this.props.actions.xxxx 指定调用mapDispatchToProps中*bindActionCreators定义的props即可。*/this.props.actions.propsAddUser(user);//this.props.actions.propsRemoveUser(user);//this.props.actions.propsSumUser();
    }}>
    

    **(推荐)**通过import * as xxx的形式将一个文件中的所有action creator全部导入方式实现

    //1.导入UserAction中所有的action creator
    import * as UserActions from './actions/UserAction';//2.导入redux中的bindActionCreators
    import {bindActionCreators} from 'redux';//3.定义connect的mapDispatchToProps函数
    const mapDispatchToProps = (dispatch)=>{return {/***通过bindActionCreators将UserAction.js中所有的action creator转换成dispatch(action)*形式,此处不在手动调用disptch(action)了。*/actions:bindActionCreators(UserActions,dispatch);}
    }
    //通过connect将react与Redux Store关联
    export default connect(mapStateToProps,mapDispatchToProps)(TestComponent)//4.组件内调用mapDispatchToProps中定义的映射的props
    <Button title='添加用户' onpress={()=>{let user={name:'zcmain',age:20,address:'中国上海'}//通过this.props.actions.xxxx 指定调用UserAction.js中具体的action即可。this.props.actions.addUser(user);//this.props.actions.removeUser(user);//this.props.actions.sumUser();
    }}>
    

对于异步Action如何使用bindActionCreator

在我们使用redux-thunk时候通过创建返回函数方式实现异步Action,那么在bindActionCreators中如何使用异步Action呢?其实与同步Action使用没有太大区别。如果异步Action调用的函数有返回值,并且通过bindActionCreator绑定次异步函数后,我们在通过this.props.xxxx (xxx为函数名)调用异步函数时候直接接受返回值。

异步Action(UserAction.js)

//通过创建返回函数方式创建Action
const addUser = (user)=>{return (dispatch)=>{//异步Action有返回值return await new Promise((resolve, reject) => {//模拟异步网络请求setTimeout(() => {dispatch(changeLoginBtnEnable(true));let userInfo = {userId: 10001,realName: 'zcmain',address: '中国上海',};dispatch(updateUserInfoVo(userInfo));resolve('success');}, 2000);});}}

bingActionCreators进行绑定异步Action

...
import {UserActions} from './actions/UserAction';const mapDispatchToProps = (dispatch)=>{return{//通过bindActionCreatros将action creator转换成dispatch(action)actions:bindActionCreatros(UserActions,dispatch);}
}

组件中使用异步Action

...
<Button title='添加用户',onPress={()=>{let user={id:'1',name:'zcmain',}/***通过bindActionCreator绑定后的action creator的调用函数如果有返回值,*通过this.props.属性调用时候直接接受返回*/this.props.actions.addUser(user).then((response)=>{//TOOD...console.log('成功:' + JSON.stringify(response);},(error)=>{console.log('失败:' + error.message);}).catch((exception)=>{console.log('异常:' + JSON.stringify(exception));});
}}/>

bindActionCreators源码解析

  1. 判断传入的参数是否是object,如果是函数,就直接返回一个包裹dispatch的函数;
  2. 如果是object,就根据相应的key,生成包裹dispatch的函数即可;
/***bindActionCreators函数*参数说明:*@param actionCreators: action create函数,可以是一个单函数,也可以是一个对象,这个对象的所有元素*都是action create函数;*@param dispatch: store.dispatch方法;*/
export default function bindActionCreators(actionCreators, dispatch) {/***如果actionCreators是一个函数的话,就调用bindActionCreator方法对action create函数*和dispatch进行绑定。*/if (typeof actionCreators === 'function') {return bindActionCreator(actionCreators, dispatch)}/***如果actionCreators不是一个对象或者actionCreators为空,则报错*/if (typeof actionCreators !== 'object' || actionCreators === null) {throw new Error('bindActionCreators expected an object or a function, + 'instead received ${actionCreators === null ?+'null' : typeof actionCreators}.' +'Did you write "import ActionCreators from" instead of +'"import * as ActionCreators from"?')}//否则actionCreators是一个对象获取所有action create函数的名字const keys = Object.keys(actionCreators)//遍历actionCreators数组对象保存dispatch和action create函数进行绑定之后的集合const boundActionCreators = {}for (let i = 0; i < keys.length; i++) {const key = keys[i]const actionCreator = actionCreators[key]// 排除值不是函数的action createif (typeof actionCreator === 'function') {// 进行绑定boundActionCreators[key] = bindActionCreator(actionCreator, dispatch)}}//返回绑定之后的对象return boundActionCreators
}/***bindActionCreator函数*/
function bindActionCreator(actionCreator, dispatch) {// 这个函数的主要作用就是返回一个函数,当我们调用返回的这个函数的时候,就会自动的dispatch对应的action// 这一块其实可以更改成如下这种形式更好// return function(...args) {return dispatch(actionCreator.apply(this, args))}return function() { return dispatch(actionCreator.apply(this, arguments)) }
}

高级

异步Action

前面我们将的action创建都是同步状态,当dispatch(action)时候,state会被立即更新。

创建同步Action(返回的是一个action对象):

//创建同步action
export const syncAddItem = {type:'addItem',text:'增加一条数据',
}
//或者通过函数创建(可接收参数,返回action)
export const syncAddItem=(desc)=>{return{type:'addItem',text:desc,}
}//通过store触发同步action,State会立即被更新
store.dispatch(syncAddItem);
store.dispatch(syncAddItem('增加一条数据'));

对于异步action创建我们需要借助**Redux Thunk**中间件。 action创建函数除了返回action对象外还可以返回函数。这时这个action创建函数就成为了thunk

什么是(为什么使用)Redux Thunk?

我们之所以需要使用诸如 Redux-Thunk 之类的中间件,是因为 Redux 存储仅支持同步数据流。 于是,中间件来救援了! 中间件允许异步数据流,解释您分派的任何内容,并最终返回一个允许同步 Redux 数据流继续的普通对象。 因此,Redux 中间件可以解决许多关键的异步需求(例如 axios 请求)。

Redux Thunk 中间件允许您编写返回函数替代返回action对象。可以使用thunk中间件来进行延迟动作的分派,或者仅在满足某个条件时才分发。内部函数接收store的dispatchgetState作为参数。

当action创建函数返回函数时,这个函数会被Redux Thunk middleWare执行(如下创建的异步Action返回的return (dispatch)函数会被Thunk中间件执行),这个函数并不需要保持纯净;它可以带有副作用,包括执行异步API请求。这个函数还可以执行dispatch(action),就像dispatch同步的Action一样。

Redux Thunk在异步Action中使用

1. 创建异步Action(返回是一个函数会被Thunk中间件调用):

//创建一个异步的action,
export const asyncAction1 = (str) => {//返回一个接收dispatch参数的函数(该函数会被Thunk中间件调用),return (dispatch) => {//2秒后指定其他操作,比如触发dispatch(action)更新StatesetTimeout(() => {//dispatch(action);console.log(str);}, 2000);};
};//storet通过dispatch方法分发异步Action
store.dispatch(asyncAction1('异步Action创建函数'))

当然异步Action返回函数除了接收dispatch参数外还可以接受getState参数,我们可以根据getState中的状态来进行逻辑判断执行不同的dispatch:

//创建一个异步的action,
export const asyncAction1 = (str) => {//返回一个接收dispatch和getState参数的函数(该函数会被Thunk中间件调用),return (dispatch,getState) => {//通过getState获取State中的counter属性,如果为偶数则返回不触发dispatchconst {counter} = getState();if(counter % 2 === 0){return;}//否则2秒后指定其他操作,比如触发dispatch(action)更新StatesetTimeout(() => {//dispatch(action);console.log(str);}, 2000);};
};//store调用dispatch方法
store.dispatch(asyncAction1('异步Action创建函数接收dispatch和getState属性'))

异步Action返回函数除了可以接收dispatchgetState两个参数以外,还可以通过Redux Thunk 使用withExtraArgument 函数注入自定义参数:

//通过Redux Thunk的withExtraArgument注入自定义参数到异步Action返回函数中
import {createStore,applyMiddleWare} from 'redux';
import thunk from 'redux-thunk';//单个参数注入
const name ='zcmain';//多个参数包装成对象注入
const age = 20,
const city = 'ShanHai',
const userInfo = {name,agecity,
}const store = createStore(reducer,//创建Store时候将自定的参数通过thunk.withExtraArgument注入到异步Action返回函数中applyMiddleware(thunk.withExtraArgument(name,userInfo)),
);
//异步Action
const asyncAction1 = ()=>{/***返回函数接受三个参数,其中 name、userInfo 是通过*Thunk.withExtraArgument(name,userInfo)注入的自定义参数。*/return (dispatch,getState,name,userInfo)=>{//TODO...you can use name and userInfo here}
}

Thunk middleware 中间件调用的函数可以有返回值,它会被当作 dispatch 方法的返回值传递。

//创建一个异步的Action
export const asyncAction2 = (url) => {//返回一个接收dispatch参数的函数return (dispatch) => {/***thunk middleWare调用的函数返回一个Promise对象,它会被当作 dispatch 方法的返回值传递,*这里通过Fetch网络请求,响应结果后调用dispatch(action)来更新State。*注意:*  不要使用 catch,因为会捕获,在 dispatch 和渲染中出现的任何错误,*  导致 'Unexpected batch number' 错误。*  https://github.com/facebook/react/issues/6895*/return fetch(url).then((response) =>{response.json()},(error)=>{}).then((json) => {//收到相应后发送dispatch(action)来更新Statedispatch({type:'UPDATA',data:json});return json;});};
};/***store通过dispatch方法触发异步Action,因为该action返回函数有返回值,*会被当作是dispatch方法的返回值传递。*/
store.dispatch(asyncAction2('http://xxx.xxx.xxx.xxx:xxx/test/json)).then((json)=>{//TODO...}).catch(()=>{//TODO...
});

注意:

Thunk middleWare执行有返回值的函数中不要使用 catch,因为会捕获,在 dispatch 和渲染中出现的任何错误, Unexpected batch number 错误

https://github.com/facebook/react/issues/6895

2. 使用异步Action

上面我们创建好了异步的Action,接下来我们需要通过Redux Thunk中间件来使用该异步Action

  1. 安装redux-thunk库:

    npm -i --save redux-thunk
    #或者
    yarn add redux-thunk
    
  2. 通过Redux的applyMiddleWare使用Redux Thunk来创建store:

    AppStore.js

    //引入createStore、applyMiddleWare
    import {createStore,applyMiddleWare} from 'redux';
    //引入thunk中间件
    import thunk from 'redux-thunk';
    //引入reducers
    import reducers from '../reducers/AppReducers';//指定reducer和中间件来创建store
    const store = createStore(reducers,applyMiddleWare(thunk));//导出store
    export default store;
    
  3. 其他组件使用

    LoginComponent.js

    /***登陆页面的mapDispatchToProps函数中使用dispatch方法触发异步的action,会在两秒后打印日志;*注意:mapDispatchToProps函数要依赖react-redux的Provider和connect。*/
    const mapDispatchToProps = (dispatch) => {return {onclick: () => {dispatch(LoginAction.asyncAction1('发送异步action'));},};
    };//两秒后会打印
    LOG  str:发送异步Actoin啦...
    
    /***dispatch分发有有返回值的异步asyncAction2*/
    const mapDispatchToProps = (dispatch) => {return {onclick: () => {dispatch(LoginAction.asyncAction2('发送异步action接受返回值')).then((json)=>{console.log('dispatch 分发异步Action并接收返回值:' + json)});},};
    };
    

范式化数据

为什么要设置范式化state数据结构?

事实上,大部分程序处理的数据都是嵌套或互相关联的,那么如何在state中使用嵌套及重复的数据(对象复用)?例如:

const blogPosts = [{id : "post1",author : {username : "user1", name : "User 1"},body : "......",comments : [{id : "comment1",author : {username : "user2", name : "User 2"},comment : ".....",},{id : "comment2",author : {username : "user3", name : "User 3"},comment : ".....",}]    },{id : "post2",author : {username : "user2", name : "User 2"},body : "......",comments : [{id : "comment3",author : {username : "user3", name : "User 3"},comment : ".....",},{id : "comment4",author : {username : "user1", name : "User 1"},comment : ".....",},{id : "comment5",author : {username : "user3", name : "User 3"},comment : ".....",}]    }// and repeat many times
]

上面的数据结构比较复杂,并且有部分数据是重复的。这里还存在一些让人关心的问题:

  • 难以保证所有复用的数据同时更新:当数据在多处冗余后,需要更新时,很难保证所有的数据都进行更新。
  • 嵌套复杂度高:嵌套的数据意味着 reducer 逻辑嵌套更多、复杂度更高。尤其是在打算更新深层嵌套数据时。
  • 不可变的数据在更新时需要状态树的祖先数据进行复制和更新,并且新的对象引用会导致与之 connect 的所有 UI 组件都重复 render。尽管要显示的数据没有发生任何改变,对深层嵌套的数据对象进行更新也会强制完全无关的 UI 组件重复 render。

正因为如此,在 Redux Store 中管理关系数据或嵌套数据的推荐做法是将这一部分视为数据库,并且将数据按范式化存储。

设计范式化State数据结构

范式化结构包含以下几个方面:

  1. 任何类型的数据在state中都有自己的"表";
  2. 任何 “数据表” 应将各个项目存储在对象中,其中每个项目的 ID 作为 key,项目本身作为 value
  3. 任何对单个项目的引用都应该根据存储项目的 ID来完成。
  4. ID 数组应该用于排序。

上面博客示例中的 state 结构范式化之后可能如下:

authorcomments对象提取出来通过byId来使用

{posts : {byId : {"post1" : {id : "post1",author : "user1",body : "......",comments : ["comment1", "comment2"]    },"post2" : {id : "post2",author : "user2",body : "......",comments : ["comment3", "comment4", "comment5"]    }}allIds : ["post1", "post2"]},comments : {byId : {"comment1" : {id : "comment1",author : "user2",comment : ".....",},"comment2" : {id : "comment2",author : "user3",comment : ".....",},"comment3" : {id : "comment3",author : "user3",comment : ".....",},"comment4" : {id : "comment4",author : "user1",comment : ".....",},"comment5" : {id : "comment5",author : "user3",comment : ".....",},},allIds : ["comment1", "comment2", "comment3", "commment4", "comment5"]},users : {byId : {"user1" : {username : "user1",name : "User 1",}"user2" : {username : "user2",name : "User 2",}"user3" : {username : "user3",name : "User 3",}},allIds : ["user1", "user2", "user3"]}
}

表间关系

因为我们将Redux Store视为数据库,所以在很多数据库设计规则里面也是同样适用的。例如:对于多对多的关系,可以设计一张中间表用于存储相关联的项目ID(经常被称为相关表或者关联表)。为了一致性起见,我们还会使用相同的byIdallIds用于实际的数据项表中。

entities:{authors:{byId:{},allIds:[]},book:{byId:{},allIds:[]},authorBook:{byId:{1:{id : 1,authorId : 5,bookId : 22},2:{id : 2,authorId : 5,bookId : 15,}},allIds:[1,2]}  
}

嵌套数据范式化

因为 API 经常以嵌套的形式发送返回数据,所以该数据需要在引入状态树之前转化为规范化形态。Normalizr 库可以帮助你实现这个。你可以定义 schema 的类型和关系,将 schema 和响应数据提供给 Normalizr,他会输出响应数据的范式化变换。输出可以放在 action 中,用于 store 的更新。有关其用法的更多详细信息,请参阅 Normalizr 文档。

管理范式化数据

当数据存在ID、嵌套或者关联关系时,应当以范式化形式存储:对象只能存储一次,ID作为键值,对象间通过ID相互引用。

将Store类比于数据库,每一项都是独立的"表"。normalizr、redux-orm此类的库能在管理规范化数据时提供参考和抽象。

中间件使用

redux-thunk

Redux Thunk是Redux中提供异步Action处理的中间件,具体使用参考上面文章《什么是Redux Thunk

redux-saga

redux-saga也是用于解决RN中异步交互的问题,与redux-thunk目标一致,不同点在于:

  • redux-thunk:

    • 介绍:是redux推出一个MiddleWare,使用简单,允许action 创建函数除了返回 action 对象外还可以返回函数,并且该返回函数可以接受dispatchgetState作为参数。这个函数并不需要保持纯净;它还可以带有副作用,包括执行异步 API 请求。这个函数还可以 dispatch action。
    • 优点:代码量小,上手简单适合轻小型应用程序中。
    • 缺点:返回函数内部复杂,不易维护。由于thunk使得Action创建函数返回不再是一个action对象,而是一个函数,而函数的内部可以多种多样,甚至更为复杂,显然使得action不易于维护。
  • redux-saga

    • 介绍:官网上的描述redux-saga 是一个用于管理应用程序 Side Effect(副作用,例如异步获取数据,访问浏览器缓存等)的 library,它的目标是让副作用管理更容易,执行更高效,测试更简单
    • 优点:避免回调地狱(当前thunk 使用async/await也可以解决),方便测试和维护,适合大型应用程序。
    • 缺点:陡峭学习路线,样板代码量大。

    在许多正常情况下和中小型应用程序中,使用async / await风格redux-thunk。它可以为你节省很多样板代码/操作/类型,而且你不需要在很多不同的sagas.ts之间切换,也不需要维护-一个特定的sagas树。但是,如果你正在开发一个大型的应用程序,其中包含非常复杂的异步,并且需要一些特性,比如并发/并行模式,或者对测试和维护有很高的需求(尤其是在测试驱动开发中),那么redux -sagas可能会拯救你的生命。

    参见《Redux-Thunk vs. Redux-Saga》

redux-ignore

redux-ignore可以指定reducer函数触发条件(例如:指定某个/某些actions才会触发当前reducer函数)。

对于通过combineReducers合并拆分的Reducer来说,触发每个 action 都会调用 所有的 reducer,JavaScript 引擎有足够的能力在每秒运行大量的函数调用,而且大部分的子 reducer 只是使用 switch 语句,并且针对大部分 action 返回的都是默认的 state。如果你仍然关心 reducer 的性能,可以使用类似 redux-ignore工具,确保只有某些action会调用一个 reducer 或几个reducer。

  • 安装redux-ignore

    npm -i --save redux-ignore
    #或者
    yarn add redux-ignore
    
  • 配置combineReducer组合Reducer,并通过filterActions(也可通过ignoreActions忽略指定的action)指定能够触发Reducer执行的action

    import {combineReducers} from 'redux';
    import {filterActions} from 'redux-ignore/src';
    import {reducerA} from './ReducerA';
    import {reducerB} from './ReducerB';//通过combineReducers将分散的Reducer组合成一个reducers
    const reducers = combineReducers(reducerA:fileterAction(reducerA,/***指定能够触发reducerA执行的Action数组,只有dispatch该数组内的action,*reducerA才会调用。*/['actionA1',       'actionA2'...]),reducerB:fileterAction(reducerB,/***指定能够触发reducerB执行的Action数组,只有dispatch该数组内的action,*reducerA才会调用。*/['actionB1','actionB2'...]);
    )
    

redux-router

redux-promise

reselect

为什么需要reselect?

在使用react-redux时候mapStateToProps 函数接收整个 Redux store 的 state 作为 props,然后返回一个传入到组件 props 的对象。该函数被称之为 selector选择器。

我们来看一个mapStateToProps函数

// mapStateToProps 就是一个 selector,每次组件更新的时候就会被调用
// 【缺点】每次组件更新的时候都会重新计算 visibleTodos,如果计算量比较大,会造成性能问题
const mapStateToProps = (state) => ({visibleTodos: selectTodos(state.todos, state.visibilityFilter),visibilityFilter: state.visibilityFilter
});// 一个 state 计算函数
export const selectTodos = (todos, filter) => {switch (filter) {case 'SHOW_ALL':return todoscase 'SHOW_COMPLETED':return todos.filter(todo => todo.completed)case 'SHOW_ACTIVE':return todos.filter(todo => !todo.completed)}
}

之前 connect 函数实现的时候,我们知道映射 props 的函数被 store.subscribe() 了,因此每次组件更新的时候,无论 state 是否改变,都会调用 mapStateToProps,而 mapStateToProps 在计算 state 的时候就会调用 state 计算函数selectTodos,过程 如下:

store.subscribe()(注册事件) —>状态更新时调用 mapStateToProps(一个selector,返回 state) —> 调用 state 计算函数 selectTodos

那么,问题 来了,如果 slector 的计算量比较大,每次更新的重新计算就会造成性能问题。

而解决性能问题的 出发点 就是:避免不必要的计算

解决问题的方式:从 selector 着手,即 mapStateToProps,如果 selector 接受的状态参数不变,那么就不调用计算函数,直接利用之前的结果。

什么是reselect?

reselect 其实就是 redux 的一个中间件,它通过传入的多个state计算获得新的 state,然后传递到 Redux Store。其主要就是进行了中间的那一步计算,使得计算的状态被缓存,从而根据传入的 state 判断是否需要调用计算函数(selectTodos),而不用在组件每次更新的时候都进行调用,从而更加高效。

参考文献

  • Redux 中文文档
  • Redux 常见问题
查看全文
如若内容造成侵权/违法违规/事实不符,请联系编程学习网邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!

相关文章

  1. vml兼容IE8报错groupClass等问题不能调用方法

    今天给一个老的jsp项目,改流程图出不来的问题,流程图用的什么框架不太清楚,是一个js文件,文件最后生成各种<v:oval> <v:roundrect> <v:shadow>等标签。浏览器:电脑自带IE浏览器,IE9使用语言:jsp,js问题:报错取不到groupClass的属性备注:后端开发同…...

    2024/3/29 12:34:46
  2. jQuery根据文本内容text进行选择

    问题描述 <!DOCTYPE html> <head><meta charset="utf-8"><script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script> </head><body> <ul><li>北京</li><li>上海<…...

    2024/3/29 12:34:45
  3. 关关

    开发背景: ​ 为了提高开发新客户的能力,为客户提供更贴切的服务;希望通过系统对潜在客户、客户开发的过程进行记录,以便分析情况,从而提高开发新客户的业务能力,为客户提供更优质的服务。 系统大致流程 创造销售机会(广告推送、熟人介绍、线下活动…) ==》制定 或实施…...

    2024/5/1 7:48:19
  4. ubuntu18.04开机时没有GRUB选项,以及重置root口令

    UBUNTU进入GRUB 有一个很久不用的18.04的虚拟机,最近生产的ubuntu中遇到问题想要在虚拟机上试试,但是发现忘记了root。。。那第一反映肯定是进grub启动单用户模式,但无fuck可说,这虚拟机启动也太快了重启好几次都没发现grub界面 网上查到原因可能是 /etc/default/grub 文件…...

    2024/5/1 15:30:10
  5. HTML 拖放(Drag and Drop)功能的踩坑总结

    一个典型的drag操作是这样开始的:用户用鼠标选中一个可拖动的(draggable)元素,移动鼠标到一个可放置的(droppable)元素,然后释放鼠标。 在操作期间,会触发一些事件类型,有一些事件类型可能会被多次触发(比如drag 和 dragover 事件类型)具体的教程看这里:HTML 拖放 …...

    2024/5/1 6:57:56
  6. Github 上一些奇异的项目

    开发十年,就只剩下这套架构体系了!!GitHub是一个面向开源及私有软件项目的托管平台,因为只支持git 作为唯一的版本库格式进行托管,故名GitHub。是全球最大的同性交友网站,哦不,最大的程序员交流网站。GitHub是通过Git进行版本控制的软件源代码托管服务,并且,GitHub还…...

    2024/5/1 10:59:46
  7. 使用pycurl爬取一个特别的网站

    (一定请仔细看)前几天在群里面有朋友推给我一个HS链接,我就打开看了,这个链接有个特别的地方,他需要我收藏起来,才能看到他本身的页面,如果你不收藏的话,呵呵,直接给你传送到aiqiyi,而且电脑上我也看了也不行,模仿手机操作也不行,后来在网上认识了一个做网络安全的…...

    2024/5/1 8:39:52
  8. 前端小白的成长Day01

    HTML&CSS篇 1.vsCode 编码软件 :! + tab 自动生成的固定格式 2.SEO优化 Search Engine Optimization 搜索引擎(百度、google等)优化,促进关键词排名 方法:h1标签可以提高排名(最好一个网页只出现一个h1元素,h1元素太多不仅不会给网站带来好的权重,有可能被认为是作…...

    2024/5/1 16:26:07
  9. java web是什么原理?

    如果你是70、80后的程序员,你一定要看一看这篇文章,保证满满的回忆。如果你是90后,那你更要看看这篇文章,因为你能找到java web发展的历史。言归正传,Java语言能长期霸占语言排行榜一个重要的原因就是强大的web开发能力,web开发是java的基石(在EJB推出的时候当时的Sun用…...

    2024/5/1 12:08:01
  10. Mac环境下, VMware Fusion下的虚拟机( CentOS 7)的 NAT网络配置

    本帖子能够实现的效果:1、虚拟机能访问外网、虚拟机能访问Mac本机;2、Mac本机可以连接虚拟机。步骤如下:1、配置 VMware Fusion 虚拟网络配置VMware Fusion 安装完成后,会在Mac OS中新建两个网卡: vmnet1以及vmnet8(在 /Library/Preferences/VMware Fusion 下可以看到),…...

    2024/5/1 6:46:41
  11. JavaEE 从入门到放弃(一):Java EE 是个什么东西

    为什么选择 Java 想必有很多初学者会像我一样,不知选择什么语言入门。在尝试了 C、C++、C#、Python、PHP 后,我决定把 Java作 为第一门深入学习的编程语言。这个路着实有点长…不过放心,你可以大胆地选择 Java。如果说 C++ 是编程界的曹操,那 Java 就是司马懿,近三十年踏惊…...

    2024/5/1 5:41:12
  12. 新手也能看懂的 SpringBoot 异步编程指南

    新手也能看懂的 SpringBoot 异步编程指南通过本文你可以了解到下面这些知识点:Future 模式介绍以及核心思想核心线程数、最大线程数的区别,队列容量代表什么;ThreadPoolTaskExecutor 饱和策略;SpringBoot 异步编程实战,搞懂代码的执行逻辑。Future 模式异步编程在处理耗时…...

    2024/5/1 5:57:31
  13. 基于frp的udp穿透

    目录简介相关文档基于UDP的穿透方案demo测试判断是否穿透成功简介frp是一种快速反向代理,可帮助您将NAT或防火墙后面的本地服务器公开到Internet。到目前为止,它支持TCP和UDP以及HTTP和HTTPS协议,在这些协议中,请求可以通过域名转发到内部服务。frp还具有P2P连接模式。相关…...

    2024/4/8 21:04:04
  14. Code_08_NetherlandsFlag 荷兰国旗问题

    一、基础知识参考 二、代码 package basic_class_01;public class Code_08_NetherlandsFlag {public static int[] partition(int[] arr, int l, int r, int p) {int less = l - 1;int more = r + 1;while (l < more) {if (arr[l] < p) {swap(arr, ++less, l++);} else i…...

    2024/5/1 9:15:00
  15. [1-15]web upload(基于upload-labs)

    环境搭建 在官网下载phpstudy及upload-labs,将下载好的包拖到WWW根目录下,开启phpstudy的集成服务后,即可通过127.0.0.1/upload-labs访问靶场 工具Burp Suite(kali自带) 中国菜刀(针对靶场可用,现实环境推荐中国蚁剑) FireFox火狐浏览器及Switchy Omega插件(科学上网可…...

    2024/3/29 7:46:05
  16. Textview显示HTML【图文混排】实现

    废话不多说,直接上刺刀!/*** 设置HTml网页** @param text html字符串* @param view textview*/private void setHtml(String text, TextView view) {MyImageGetter myImageGetter = new MyImageGetter(context, view);CharSequence sequence;if (android.os.Build.VERSION.SD…...

    2024/5/1 10:07:05
  17. 程序员接私活怎样防止做完了不给钱?

    那么,怎么保证自己的薪酬安全呢?我们在开工前,一定要做好一些证据方面的准备(也就是“讨薪”的理论依据),这其中最重要的就是需求文档和验收标准。一定要让需求方提供这两个文档资料作为开发的基础。之后开发过程中,也要注意保护自己。我们接私活一般是两个渠道,一个是…...

    2024/5/1 5:46:51
  18. ubuntu18.04开机时没有GRUB选项

    有很久不用的一个18.04的虚拟机,最近生产中遇到问题想要在虚拟机上试试,但是发现忘记了root。。。那第一反映肯定是进grub启动单用户模式,但无fuck可说,这虚拟机启动也太快了重启好几次都没发现grub界面 网上查到原因可能是 /etc/default/grub 文件中配置项 GRUB_HIDDEN_TI…...

    2024/4/14 14:26:08
  19. 程序员Java架构师多线程面试题和回答解析

    当我们在Java架构师面试的过程中常见的多线程和并发方面的问题肯定是必不可少的一部分。那么在面试之前我们更应该多准备一些关于多线程方面的问题。 面试官只是想确信面试者有足够的Java线程与并发方面的知识,因为有很多只停留于表面的理论知识,归根结底还是功力不够扎实。下…...

    2024/3/29 7:46:01
  20. Vue + Spring Boot 项目实战(八):导航栏与图书页面设计

    文章目录前言一、导航栏的实现1.路由配置2.使用 NavMenu 组件二、图书管理页面2.1. LibraryIndex.vue2.SideMenu.vue3.Books.vue 前言 之前讲过使用 Element 辅助前端页面的开发,但是只用到了比较少的内容,这一篇我们来做一下系统的核心页面——图书管理页面的前端部分,旨在…...

    2024/3/29 16:34:37

最新文章

  1. 2024年五一杯高校数学建模竞赛(A题)|钢板切割问题 | 建模解析,小鹿学长带队指引全代码文章与思路

    我是鹿鹿学长&#xff0c;就读于上海交通大学&#xff0c;截至目前已经帮200人完成了建模与思路的构建的处理了&#xff5e; 本篇文章是鹿鹿学长经过深度思考&#xff0c;独辟蹊径&#xff0c;通过路径优化解决钢板切割问题。结合贪心算法&#xff0c;Floyd-Warshall等多元算法…...

    2024/5/1 17:10:31
  2. 梯度消失和梯度爆炸的一些处理方法

    在这里是记录一下梯度消失或梯度爆炸的一些处理技巧。全当学习总结了如有错误还请留言&#xff0c;在此感激不尽。 权重和梯度的更新公式如下&#xff1a; w w − η ⋅ ∇ w w w - \eta \cdot \nabla w ww−η⋅∇w 个人通俗的理解梯度消失就是网络模型在反向求导的时候出…...

    2024/3/20 10:50:27
  3. matlab 轨迹生成函数

    文章目录 jtrajctrajmstrajmtrajtpolylspbtrinterp用例参考链接jtraj 计算两个构型之间的关节空间轨迹 [q, qd, qdd] = jtraj(q0, qf, m)是关节空间轨迹q(MxN),其中关节坐标从q0(1xN)变化到qf(1xN)。使用五次(5阶)多项式,并默认速度和加速度为零边界条件。假设时间以m步从0…...

    2024/4/30 10:27:49
  4. xv6项目开源—05

    xv6项目开源—05.md 理论&#xff1a; 1、设备驱动程序在两种环境中执行代码&#xff1a;上半部分在进程的内核线程中运行&#xff0c;下半部分在中断时执行。上半部分通过系统调用进行调用&#xff0c;如希望设备执行I/O操作的read和write。这段代码可能会要求硬件执行操作&…...

    2024/4/30 7:09:10
  5. 逆向案例十二——看准网企业信息json格式的信息

    网址&#xff1a;【全国公司排行|排名榜单|哪家好】-看准网 打开开发者工具——刷新——网络——XHR——下滑页面加载新的页面——找到数据包 发现参数加密&#xff0c;返回的数据也进行了加密 按关键字在下方搜索 kiv进入第一个js文件 ctrlf打开文件里面的搜索框继续搜kiv找到…...

    2024/5/1 13:34:19
  6. 【外汇早评】美通胀数据走低,美元调整

    原标题:【外汇早评】美通胀数据走低,美元调整昨日美国方面公布了新一期的核心PCE物价指数数据,同比增长1.6%,低于前值和预期值的1.7%,距离美联储的通胀目标2%继续走低,通胀压力较低,且此前美国一季度GDP初值中的消费部分下滑明显,因此市场对美联储后续更可能降息的政策…...

    2024/4/29 23:16:47
  7. 【原油贵金属周评】原油多头拥挤,价格调整

    原标题:【原油贵金属周评】原油多头拥挤,价格调整本周国际劳动节,我们喜迎四天假期,但是整个金融市场确实流动性充沛,大事频发,各个商品波动剧烈。美国方面,在本周四凌晨公布5月份的利率决议和新闻发布会,维持联邦基金利率在2.25%-2.50%不变,符合市场预期。同时美联储…...

    2024/4/30 18:14:14
  8. 【外汇周评】靓丽非农不及疲软通胀影响

    原标题:【外汇周评】靓丽非农不及疲软通胀影响在刚结束的周五,美国方面公布了新一期的非农就业数据,大幅好于前值和预期,新增就业重新回到20万以上。具体数据: 美国4月非农就业人口变动 26.3万人,预期 19万人,前值 19.6万人。 美国4月失业率 3.6%,预期 3.8%,前值 3…...

    2024/4/29 2:29:43
  9. 【原油贵金属早评】库存继续增加,油价收跌

    原标题:【原油贵金属早评】库存继续增加,油价收跌周三清晨公布美国当周API原油库存数据,上周原油库存增加281万桶至4.692亿桶,增幅超过预期的74.4万桶。且有消息人士称,沙特阿美据悉将于6月向亚洲炼油厂额外出售更多原油,印度炼油商预计将每日获得至多20万桶的额外原油供…...

    2024/4/30 18:21:48
  10. 【外汇早评】日本央行会议纪要不改日元强势

    原标题:【外汇早评】日本央行会议纪要不改日元强势近两日日元大幅走强与近期市场风险情绪上升,避险资金回流日元有关,也与前一段时间的美日贸易谈判给日本缓冲期,日本方面对汇率问题也避免继续贬值有关。虽然今日早间日本央行公布的利率会议纪要仍然是支持宽松政策,但这符…...

    2024/4/27 17:58:04
  11. 【原油贵金属早评】欧佩克稳定市场,填补伊朗问题的影响

    原标题:【原油贵金属早评】欧佩克稳定市场,填补伊朗问题的影响近日伊朗局势升温,导致市场担忧影响原油供给,油价试图反弹。此时OPEC表态稳定市场。据消息人士透露,沙特6月石油出口料将低于700万桶/日,沙特已经收到石油消费国提出的6月份扩大出口的“适度要求”,沙特将满…...

    2024/4/27 14:22:49
  12. 【外汇早评】美欲与伊朗重谈协议

    原标题:【外汇早评】美欲与伊朗重谈协议美国对伊朗的制裁遭到伊朗的抗议,昨日伊朗方面提出将部分退出伊核协议。而此行为又遭到欧洲方面对伊朗的谴责和警告,伊朗外长昨日回应称,欧洲国家履行它们的义务,伊核协议就能保证存续。据传闻伊朗的导弹已经对准了以色列和美国的航…...

    2024/4/28 1:28:33
  13. 【原油贵金属早评】波动率飙升,市场情绪动荡

    原标题:【原油贵金属早评】波动率飙升,市场情绪动荡因中美贸易谈判不安情绪影响,金融市场各资产品种出现明显的波动。随着美国与中方开启第十一轮谈判之际,美国按照既定计划向中国2000亿商品征收25%的关税,市场情绪有所平复,已经开始接受这一事实。虽然波动率-恐慌指数VI…...

    2024/4/30 9:43:09
  14. 【原油贵金属周评】伊朗局势升温,黄金多头跃跃欲试

    原标题:【原油贵金属周评】伊朗局势升温,黄金多头跃跃欲试美国和伊朗的局势继续升温,市场风险情绪上升,避险黄金有向上突破阻力的迹象。原油方面稍显平稳,近期美国和OPEC加大供给及市场需求回落的影响,伊朗局势并未推升油价走强。近期中美贸易谈判摩擦再度升级,美国对中…...

    2024/4/27 17:59:30
  15. 【原油贵金属早评】市场情绪继续恶化,黄金上破

    原标题:【原油贵金属早评】市场情绪继续恶化,黄金上破周初中国针对于美国加征关税的进行的反制措施引发市场情绪的大幅波动,人民币汇率出现大幅的贬值动能,金融市场受到非常明显的冲击。尤其是波动率起来之后,对于股市的表现尤其不安。隔夜美国股市出现明显的下行走势,这…...

    2024/4/25 18:39:16
  16. 【外汇早评】美伊僵持,风险情绪继续升温

    原标题:【外汇早评】美伊僵持,风险情绪继续升温昨日沙特两艘油轮再次发生爆炸事件,导致波斯湾局势进一步恶化,市场担忧美伊可能会出现摩擦生火,避险品种获得支撑,黄金和日元大幅走强。美指受中美贸易问题影响而在低位震荡。继5月12日,四艘商船在阿联酋领海附近的阿曼湾、…...

    2024/4/28 1:34:08
  17. 【原油贵金属早评】贸易冲突导致需求低迷,油价弱势

    原标题:【原油贵金属早评】贸易冲突导致需求低迷,油价弱势近日虽然伊朗局势升温,中东地区几起油船被袭击事件影响,但油价并未走高,而是出于调整结构中。由于市场预期局势失控的可能性较低,而中美贸易问题导致的全球经济衰退风险更大,需求会持续低迷,因此油价调整压力较…...

    2024/4/26 19:03:37
  18. 氧生福地 玩美北湖(上)——为时光守候两千年

    原标题:氧生福地 玩美北湖(上)——为时光守候两千年一次说走就走的旅行,只有一张高铁票的距离~ 所以,湖南郴州,我来了~ 从广州南站出发,一个半小时就到达郴州西站了。在动车上,同时改票的南风兄和我居然被分到了一个车厢,所以一路非常愉快地聊了过来。 挺好,最起…...

    2024/4/29 20:46:55
  19. 氧生福地 玩美北湖(中)——永春梯田里的美与鲜

    原标题:氧生福地 玩美北湖(中)——永春梯田里的美与鲜一觉醒来,因为大家太爱“美”照,在柳毅山庄去寻找龙女而错过了早餐时间。近十点,向导坏坏还是带着饥肠辘辘的我们去吃郴州最富有盛名的“鱼头粉”。说这是“十二分推荐”,到郴州必吃的美食之一。 哇塞!那个味美香甜…...

    2024/4/30 22:21:04
  20. 氧生福地 玩美北湖(下)——奔跑吧骚年!

    原标题:氧生福地 玩美北湖(下)——奔跑吧骚年!让我们红尘做伴 活得潇潇洒洒 策马奔腾共享人世繁华 对酒当歌唱出心中喜悦 轰轰烈烈把握青春年华 让我们红尘做伴 活得潇潇洒洒 策马奔腾共享人世繁华 对酒当歌唱出心中喜悦 轰轰烈烈把握青春年华 啊……啊……啊 两…...

    2024/5/1 4:32:01
  21. 扒开伪装医用面膜,翻六倍价格宰客,小姐姐注意了!

    原标题:扒开伪装医用面膜,翻六倍价格宰客,小姐姐注意了!扒开伪装医用面膜,翻六倍价格宰客!当行业里的某一品项火爆了,就会有很多商家蹭热度,装逼忽悠,最近火爆朋友圈的医用面膜,被沾上了污点,到底怎么回事呢? “比普通面膜安全、效果好!痘痘、痘印、敏感肌都能用…...

    2024/4/27 23:24:42
  22. 「发现」铁皮石斛仙草之神奇功效用于医用面膜

    原标题:「发现」铁皮石斛仙草之神奇功效用于医用面膜丽彦妆铁皮石斛医用面膜|石斛多糖无菌修护补水贴19大优势: 1、铁皮石斛:自唐宋以来,一直被列为皇室贡品,铁皮石斛生于海拔1600米的悬崖峭壁之上,繁殖力差,产量极低,所以古代仅供皇室、贵族享用 2、铁皮石斛自古民间…...

    2024/4/28 5:48:52
  23. 丽彦妆\医用面膜\冷敷贴轻奢医学护肤引导者

    原标题:丽彦妆\医用面膜\冷敷贴轻奢医学护肤引导者【公司简介】 广州华彬企业隶属香港华彬集团有限公司,专注美业21年,其旗下品牌: 「圣茵美」私密荷尔蒙抗衰,产后修复 「圣仪轩」私密荷尔蒙抗衰,产后修复 「花茵莳」私密荷尔蒙抗衰,产后修复 「丽彦妆」专注医学护…...

    2024/4/30 9:42:22
  24. 广州械字号面膜生产厂家OEM/ODM4项须知!

    原标题:广州械字号面膜生产厂家OEM/ODM4项须知!广州械字号面膜生产厂家OEM/ODM流程及注意事项解读: 械字号医用面膜,其实在我国并没有严格的定义,通常我们说的医美面膜指的应该是一种「医用敷料」,也就是说,医用面膜其实算作「医疗器械」的一种,又称「医用冷敷贴」。 …...

    2024/4/30 9:43:22
  25. 械字号医用眼膜缓解用眼过度到底有无作用?

    原标题:械字号医用眼膜缓解用眼过度到底有无作用?医用眼膜/械字号眼膜/医用冷敷眼贴 凝胶层为亲水高分子材料,含70%以上的水分。体表皮肤温度传导到本产品的凝胶层,热量被凝胶内水分子吸收,通过水分的蒸发带走大量的热量,可迅速地降低体表皮肤局部温度,减轻局部皮肤的灼…...

    2024/4/30 9:42:49
  26. 配置失败还原请勿关闭计算机,电脑开机屏幕上面显示,配置失败还原更改 请勿关闭计算机 开不了机 这个问题怎么办...

    解析如下&#xff1a;1、长按电脑电源键直至关机&#xff0c;然后再按一次电源健重启电脑&#xff0c;按F8健进入安全模式2、安全模式下进入Windows系统桌面后&#xff0c;按住“winR”打开运行窗口&#xff0c;输入“services.msc”打开服务设置3、在服务界面&#xff0c;选中…...

    2022/11/19 21:17:18
  27. 错误使用 reshape要执行 RESHAPE,请勿更改元素数目。

    %读入6幅图像&#xff08;每一幅图像的大小是564*564&#xff09; f1 imread(WashingtonDC_Band1_564.tif); subplot(3,2,1),imshow(f1); f2 imread(WashingtonDC_Band2_564.tif); subplot(3,2,2),imshow(f2); f3 imread(WashingtonDC_Band3_564.tif); subplot(3,2,3),imsho…...

    2022/11/19 21:17:16
  28. 配置 已完成 请勿关闭计算机,win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机...

    win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机”问题的解决方法在win7系统关机时如果有升级系统的或者其他需要会直接进入一个 等待界面&#xff0c;在等待界面中我们需要等待操作结束才能关机&#xff0c;虽然这比较麻烦&#xff0c;但是对系统进行配置和升级…...

    2022/11/19 21:17:15
  29. 台式电脑显示配置100%请勿关闭计算机,“准备配置windows 请勿关闭计算机”的解决方法...

    有不少用户在重装Win7系统或更新系统后会遇到“准备配置windows&#xff0c;请勿关闭计算机”的提示&#xff0c;要过很久才能进入系统&#xff0c;有的用户甚至几个小时也无法进入&#xff0c;下面就教大家这个问题的解决方法。第一种方法&#xff1a;我们首先在左下角的“开始…...

    2022/11/19 21:17:14
  30. win7 正在配置 请勿关闭计算机,怎么办Win7开机显示正在配置Windows Update请勿关机...

    置信有很多用户都跟小编一样遇到过这样的问题&#xff0c;电脑时发现开机屏幕显现“正在配置Windows Update&#xff0c;请勿关机”(如下图所示)&#xff0c;而且还需求等大约5分钟才干进入系统。这是怎样回事呢&#xff1f;一切都是正常操作的&#xff0c;为什么开时机呈现“正…...

    2022/11/19 21:17:13
  31. 准备配置windows 请勿关闭计算机 蓝屏,Win7开机总是出现提示“配置Windows请勿关机”...

    Win7系统开机启动时总是出现“配置Windows请勿关机”的提示&#xff0c;没过几秒后电脑自动重启&#xff0c;每次开机都这样无法进入系统&#xff0c;此时碰到这种现象的用户就可以使用以下5种方法解决问题。方法一&#xff1a;开机按下F8&#xff0c;在出现的Windows高级启动选…...

    2022/11/19 21:17:12
  32. 准备windows请勿关闭计算机要多久,windows10系统提示正在准备windows请勿关闭计算机怎么办...

    有不少windows10系统用户反映说碰到这样一个情况&#xff0c;就是电脑提示正在准备windows请勿关闭计算机&#xff0c;碰到这样的问题该怎么解决呢&#xff0c;现在小编就给大家分享一下windows10系统提示正在准备windows请勿关闭计算机的具体第一种方法&#xff1a;1、2、依次…...

    2022/11/19 21:17:11
  33. 配置 已完成 请勿关闭计算机,win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机”的解决方法...

    今天和大家分享一下win7系统重装了Win7旗舰版系统后&#xff0c;每次关机的时候桌面上都会显示一个“配置Windows Update的界面&#xff0c;提示请勿关闭计算机”&#xff0c;每次停留好几分钟才能正常关机&#xff0c;导致什么情况引起的呢&#xff1f;出现配置Windows Update…...

    2022/11/19 21:17:10
  34. 电脑桌面一直是清理请关闭计算机,windows7一直卡在清理 请勿关闭计算机-win7清理请勿关机,win7配置更新35%不动...

    只能是等着&#xff0c;别无他法。说是卡着如果你看硬盘灯应该在读写。如果从 Win 10 无法正常回滚&#xff0c;只能是考虑备份数据后重装系统了。解决来方案一&#xff1a;管理员运行cmd&#xff1a;net stop WuAuServcd %windir%ren SoftwareDistribution SDoldnet start WuA…...

    2022/11/19 21:17:09
  35. 计算机配置更新不起,电脑提示“配置Windows Update请勿关闭计算机”怎么办?

    原标题&#xff1a;电脑提示“配置Windows Update请勿关闭计算机”怎么办&#xff1f;win7系统中在开机与关闭的时候总是显示“配置windows update请勿关闭计算机”相信有不少朋友都曾遇到过一次两次还能忍但经常遇到就叫人感到心烦了遇到这种问题怎么办呢&#xff1f;一般的方…...

    2022/11/19 21:17:08
  36. 计算机正在配置无法关机,关机提示 windows7 正在配置windows 请勿关闭计算机 ,然后等了一晚上也没有关掉。现在电脑无法正常关机...

    关机提示 windows7 正在配置windows 请勿关闭计算机 &#xff0c;然后等了一晚上也没有关掉。现在电脑无法正常关机以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容&#xff0c;让我们赶快一起来看一下吧&#xff01;关机提示 windows7 正在配…...

    2022/11/19 21:17:05
  37. 钉钉提示请勿通过开发者调试模式_钉钉请勿通过开发者调试模式是真的吗好不好用...

    钉钉请勿通过开发者调试模式是真的吗好不好用 更新时间:2020-04-20 22:24:19 浏览次数:729次 区域: 南阳 > 卧龙 列举网提醒您:为保障您的权益,请不要提前支付任何费用! 虚拟位置外设器!!轨迹模拟&虚拟位置外设神器 专业用于:钉钉,外勤365,红圈通,企业微信和…...

    2022/11/19 21:17:05
  38. 配置失败还原请勿关闭计算机怎么办,win7系统出现“配置windows update失败 还原更改 请勿关闭计算机”,长时间没反应,无法进入系统的解决方案...

    前几天班里有位学生电脑(windows 7系统)出问题了&#xff0c;具体表现是开机时一直停留在“配置windows update失败 还原更改 请勿关闭计算机”这个界面&#xff0c;长时间没反应&#xff0c;无法进入系统。这个问题原来帮其他同学也解决过&#xff0c;网上搜了不少资料&#x…...

    2022/11/19 21:17:04
  39. 一个电脑无法关闭计算机你应该怎么办,电脑显示“清理请勿关闭计算机”怎么办?...

    本文为你提供了3个有效解决电脑显示“清理请勿关闭计算机”问题的方法&#xff0c;并在最后教给你1种保护系统安全的好方法&#xff0c;一起来看看&#xff01;电脑出现“清理请勿关闭计算机”在Windows 7(SP1)和Windows Server 2008 R2 SP1中&#xff0c;添加了1个新功能在“磁…...

    2022/11/19 21:17:03
  40. 请勿关闭计算机还原更改要多久,电脑显示:配置windows更新失败,正在还原更改,请勿关闭计算机怎么办...

    许多用户在长期不使用电脑的时候&#xff0c;开启电脑发现电脑显示&#xff1a;配置windows更新失败&#xff0c;正在还原更改&#xff0c;请勿关闭计算机。。.这要怎么办呢&#xff1f;下面小编就带着大家一起看看吧&#xff01;如果能够正常进入系统&#xff0c;建议您暂时移…...

    2022/11/19 21:17:02
  41. 还原更改请勿关闭计算机 要多久,配置windows update失败 还原更改 请勿关闭计算机,电脑开机后一直显示以...

    配置windows update失败 还原更改 请勿关闭计算机&#xff0c;电脑开机后一直显示以以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容&#xff0c;让我们赶快一起来看一下吧&#xff01;配置windows update失败 还原更改 请勿关闭计算机&#x…...

    2022/11/19 21:17:01
  42. 电脑配置中请勿关闭计算机怎么办,准备配置windows请勿关闭计算机一直显示怎么办【图解】...

    不知道大家有没有遇到过这样的一个问题&#xff0c;就是我们的win7系统在关机的时候&#xff0c;总是喜欢显示“准备配置windows&#xff0c;请勿关机”这样的一个页面&#xff0c;没有什么大碍&#xff0c;但是如果一直等着的话就要两个小时甚至更久都关不了机&#xff0c;非常…...

    2022/11/19 21:17:00
  43. 正在准备配置请勿关闭计算机,正在准备配置windows请勿关闭计算机时间长了解决教程...

    当电脑出现正在准备配置windows请勿关闭计算机时&#xff0c;一般是您正对windows进行升级&#xff0c;但是这个要是长时间没有反应&#xff0c;我们不能再傻等下去了。可能是电脑出了别的问题了&#xff0c;来看看教程的说法。正在准备配置windows请勿关闭计算机时间长了方法一…...

    2022/11/19 21:16:59
  44. 配置失败还原请勿关闭计算机,配置Windows Update失败,还原更改请勿关闭计算机...

    我们使用电脑的过程中有时会遇到这种情况&#xff0c;当我们打开电脑之后&#xff0c;发现一直停留在一个界面&#xff1a;“配置Windows Update失败&#xff0c;还原更改请勿关闭计算机”&#xff0c;等了许久还是无法进入系统。如果我们遇到此类问题应该如何解决呢&#xff0…...

    2022/11/19 21:16:58
  45. 如何在iPhone上关闭“请勿打扰”

    Apple’s “Do Not Disturb While Driving” is a potentially lifesaving iPhone feature, but it doesn’t always turn on automatically at the appropriate time. For example, you might be a passenger in a moving car, but your iPhone may think you’re the one dri…...

    2022/11/19 21:16:57