React系列十八 - Redux(四)state如何管理

简介: Redux(四)state如何管理

一. reducer拆分


1.1. reducer代码拆分


我们来看一下目前我们的reducer:

  • 当前这个reducer既有处理counter的代码,又有处理home页面的数据;
  • 后续counter相关的状态或home相关的状态会进一步变得更加复杂;
  • 我们也会继续添加其他的相关状态,比如购物车、分类、歌单等等;
function reducer(state = initialState, action) {
  switch (action.type) {
    case ADD_NUMBER:
      return { ...state, counter: state.counter + action.num };
    case SUB_NUMBER:
      return { ...state, counter: state.counter - action.num };
    case CHANGE_BANNER:
      return { ...state, banners: action.banners };
    case CHANGE_RECOMMEND:
      return { ...state, recommends: action.recommends };
    default:
      return state;
  }
}

如果将所有的状态都放到一个reducer中进行管理,随着项目的日趋庞大,必然会造成代码臃肿、难以维护。

因此,我们可以对reducer进行拆分:

我们先抽取一个对counter处理的reducer:

// counter相关的状态
const initialCounter = {
  counter: 0
}
function counterReducer(state = initialCounter, action) {
  switch (action.type) {
    case ADD_NUMBER:
      return { ...state, counter: state.counter + action.num };
    case SUB_NUMBER:
      return { ...state, counter: state.counter - action.num };
    default:
      return state;
  }
}

再抽取一个对home处理的reducer:

// home相关的状态
const initialHome = {
  banners: [],
  recommends: []
}
function homeReducer(state = initialHome, action) {
  switch (action.type) {
    case CHANGE_BANNER:
      return { ...state, banners: action.banners };
    case CHANGE_RECOMMEND:
      return { ...state, recommends: action.recommends };
    default:
      return state;
  }
}

如果将它们合并起来呢?

const initialState = {
}
function reducer(state = initialState, action) {
  return {
    counterInfo: counterReducer(state.counterInfo, action),
    homeInfo: homeReducer(state.homeInfo, action),
  }
}


1.2. reducer文件拆分


目前我们已经将不同的状态处理拆分到不同的reducer中,我们来思考:

  • 虽然已经放到不同的函数了,但是这些函数的处理依然是在同一个文件中,代码非常的混乱;
  • 另外关于reducer中用到的constant、action等我们也依然是在同一个文件中;

所以,接下来,我们可以对文件结构再次进行拆分:

./store
├── counter
│   ├── actioncreators.js
│   ├── constants.js
│   ├── index.js
│   └── reducer.js
├── home
│   ├── actioncreators.js
│   ├── constants.js
│   ├── index.js
│   └── reducer.js
├── index.js
├── reducer.js
└── saga.js

这里不再给出代码,每个文件中存放的内容即可:

  • home/actioncreators.js:存放home相关的action;
  • home/constants.js:存放home相关的常量;
  • home/reducer.js:存放分离的reducer代码;
  • index.js:统一对外暴露的内容;


1.3. combineReducers


目前我们合并的方式是通过每次调用reducer函数自己来返回一个新的对象:

import { reducer as counterReducer } from './counter';
import { reducer as homeReducer } from './home';
const initialState = {
}
function reducer(state = initialState, action) {
  return {
    counterInfo: counterReducer(state.counterInfo, action),
    homeInfo: homeReducer(state.homeInfo, action),
  }
}

事实上,redux给我们提供了一个combineReducers函数可以方便的让我们对多个reducer进行合并:

import { combineReducers } from 'redux';
import { reducer as counterReducer } from './counter';
import { reducer as homeReducer } from './home';
const reducer = combineReducers({
  counterInfo: counterReducer,
  homeInfo: homeReducer
})
export default reducer;

那么combineReducers是如何实现的呢?

  • 事实上,它也是讲我们传入的reducers合并到一个对象中,最终返回一个combination的函数(相当于我们之前的reducer函数了);
  • 在执行combination函数的过程中,它会通过判断前后返回的数据是否相同来决定返回之前的state还是新的state;
  • 新的state会触发订阅者发生对应的刷新,而旧的state可以有效的组织订阅者发生刷新;
export default function combineReducers(reducers) {
  const reducerKeys = Object.keys(reducers)
  const finalReducers = {}
  for (let i = 0; i < reducerKeys.length; i++) {
    const key = reducerKeys[i]
    if (process.env.NODE_ENV !== 'production') {
      if (typeof reducers[key] === 'undefined') {
        warning(`No reducer provided for key "${key}"`)
      }
    }
    if (typeof reducers[key] === 'function') {
      finalReducers[key] = reducers[key]
    }
  }
  const finalReducerKeys = Object.keys(finalReducers)
  // This is used to make sure we don't warn about the same
  // keys multiple times.
  let unexpectedKeyCache
  if (process.env.NODE_ENV !== 'production') {
    unexpectedKeyCache = {}
  }
  let shapeAssertionError
  try {
    assertReducerShape(finalReducers)
  } catch (e) {
    shapeAssertionError = e
  }
  return function combination(state = {}, action) {
    if (shapeAssertionError) {
      throw shapeAssertionError
    }
    if (process.env.NODE_ENV !== 'production') {
      const warningMessage = getUnexpectedStateShapeWarningMessage(
        state,
        finalReducers,
        action,
        unexpectedKeyCache
      )
      if (warningMessage) {
        warning(warningMessage)
      }
    }
    let hasChanged = false
    const nextState = {}
    for (let i = 0; i < finalReducerKeys.length; i++) {
      const key = finalReducerKeys[i]
      const reducer = finalReducers[key]
      const previousStateForKey = state[key]
      const nextStateForKey = reducer(previousStateForKey, action)
      if (typeof nextStateForKey === 'undefined') {
        const errorMessage = getUndefinedStateErrorMessage(key, action)
        throw new Error(errorMessage)
      }
      nextState[key] = nextStateForKey
      hasChanged = hasChanged || nextStateForKey !== previousStateForKey
    }
    hasChanged =
      hasChanged || finalReducerKeys.length !== Object.keys(state).length
    return hasChanged ? nextState : state
  }
}


二. ImmutableJS


2.1. 数据可变性的问题


在React开发中,我们总是会强调数据的不可变性:

  • 无论是类组件中的state,还是redux中管理的state;
  • 事实上在整个JavaScript编码过程中,数据的不可变性都是非常重要的;

数据的可变性引发的问题:

  • 我们明明没有修改obj,只是修改了obj2,但是最终obj也被我们修改掉了;
  • 原因非常简单,对象是引用类型,它们指向同一块内存空间,两个引用都可以任意修改;
const obj = {
  name: "why",
  age: 18
}
console.log(obj); // {name: "why", age: 18}
const obj2 = obj;
obj2.name = "kobe";
console.log(obj); // {name: "kobe", age: 18}

有没有办法解决上面的问题呢?

  • 进行对象的拷贝即可:Object.assign或扩展运算符
console.log(obj); // {name: "why", age: 18}
const obj2 = {...obj};
obj2.name = "kobe";
console.log(obj); // {name: "kobe", age: 18}

这种对象的浅拷贝有没有问题呢?

  • 从代码的角度来说,没有问题,也解决了我们实际开发中一些潜在风险;
  • 从性能的角度来说,有问题,如果对象过于庞大,这种拷贝的方式会带来性能问题以及内存浪费;

有人会说,开发中不都是这样做的吗?

  • 我比较喜欢一句话:从来如此,便是对的吗?


2.2. 认识ImmutableJS


为了解决上面的问题,出现了Immutable对象的概念:

  • Immutable对象的特点是只要修改了对象,就会返回一个新的对象,旧的对象不会发生改变;

但是这样的方式就不会浪费内存了吗?

  • 为了节约内存,又出现了一个新的算法:Persistent Data Structure(持久化数据结构或一致性数据结构);

当然,我们一听到持久化第一反应应该是数据被保存到本地或者数据库,但是这里并不是这个含义:

  • 用一种数据结构来保存数据;
  • 当数据被修改时,会返回一个对象,但是新的对象会尽可能的利用之前的数据结构而不会对内存造成浪费;

如何做到这一点呢?结构共享:

  • 如果在公众号等地方动图不能正常显示,可以查看下面的静态图;

image.png                                              Immutable 原理动画image.png                                             Immutable data


2.3. ImutableJS常见API


我们来学习一下ImmutableJS常用的API:

  • 注意:我这里只是演示了一些API,更多的方式可以参考官网;
const imjs = Immutable;
// 1.定义JavaScript的Array和转换成immutable的List
const friends = [
  { name: "why", age: 18 },
  { name: "kobe", age: 30 }
]
// 不会进行深层转换
const imArray1 = imjs.List(friends);
// 会进行深层转换
const imArray2 = imjs.fromJS(friends);
// console.log(imArray1);
// console.log(imArray2);
// 1.定义JavaScript的Object和转换成immutable的Map
const info = {
  name: "coderwhy",
  age: 18,
  friend: {
    name: "kobe",
    age: 30
  }
}
const imObj1 = imjs.Map(info);
const imObj2 = imjs.fromJS(info);
// console.log(imObj1);
// console.log(imObj2);
// 3.对immutable操作
// 3.1.添加数据
// 产生一个新的immutable对象
console.log(imArray1.push("aaaa"));
console.log(imArray1.set(2, "aaaa"));
// 原来的是不变的
console.log(imArray1);
// 3.2.修改数据
console.log(imArray1.set(1, "aaaa"));
console.log(imArray2.set(2, imjs.fromJS("bbbb")));
// 3.3.删除数据
console.log(imArray1.delete(0).get(0)); // {name: "kobe", age: 30}
// 3.4.查询数据
console.log(imArray1.get(1));
console.log(imArray2.get(1));
console.log(imArray1.get(1).name);
console.log(imArray2.getIn([1, "name"]));
// 4.转换成JavaScript对象
const array = imArray1.toJS();
const obj = imObj1.toJS();
console.log(array);
console.log(obj);


2.4. ImmutableJS重构redux


目前Redux已经学习了过多的内容了,很多同学已经认识到redux的难度,所以如何将ImmutableJS和redux结合使用我们这里先不讲解。

具体ImmutableJS和Redux的结合使用,放到后续项目练习时,再详细说明。


三. Redux FAQ


是否将所有的状态应用到redux


我们学习了Redux用来管理我们的应用状态,并且非常好用(当然,你学会前提下,没有学会,好好回顾一下)。

目前我们已经主要学习了三种状态管理方式:

  • 方式一:组件中自己的state管理;
  • 方式二:Context数据的共享状态;
  • 方式三:Redux管理应用状态;

在开发中如何选择呢?

  • 首先,这个没有一个标准的答案;
  • 某些用户,选择将所有的状态放到redux中进行管理,因为这样方便追踪和共享;
  • 有些用户,选择将某些组件自己的状态放到组件内部进行管理;
  • 有些用户,将类似于主题、用户信息等数据放到Context中进行共享和管理;
  • 做一个开发者,到底选择怎样的状态管理方式,是你的工作之一,可以一个最好的平衡方式(Find a balance that works for you, and go with it.);

image.pngredux作者建议

目前项目中我采用的state管理方案:

  • UI相关的组件内部可以维护的状态,在组件内部自己来维护;
  • 只要是需要共享的状态,都交给redux来管理和维护;
  • 从服务器请求的数据(包括请求的操作),交给redux来维护;

当然,根据不同的情况会进行适当的调整,在后续学习网易云音乐项目时,我也会再次讲解以实战的角度来设计数据的管理方案。

相关文章
|
11天前
|
前端开发 JavaScript
学习react基础(3)_setState、state、jsx、使用ref的几种形式
本文探讨了React中this.setState和this.state的区别,以及React的核心概念,包括核心库的使用、JSX语法、类与函数组件的区别、事件处理和ref的使用。
32 3
学习react基础(3)_setState、state、jsx、使用ref的几种形式
|
11天前
|
前端开发
学习react基础(2)_props、state和style的使用
本文介绍了React中组件间数据传递的方式,包括props和state的使用,以及如何在React组件中使用style样式。
21 0
|
2月前
|
存储 JavaScript 前端开发
React中使用redux
React中使用redux
128 56
|
2月前
|
前端开发
React组件实例更改state状态值(四)
【8月更文挑战第14天】React组件实例更改state状态值(四)
28 1
React组件实例更改state状态值(四)
|
2月前
|
前端开发 JavaScript
React组件实例state(三)
【8月更文挑战第14天】React组件实例state(三)
27 1
React组件实例state(三)
|
11天前
|
前端开发
React使用hooks遇到的坑_state中的某几个属性数据变成了空字符
本文讨论了在React使用hooks时遇到的一个问题:state中的某些属性数据变成了空字符。作者通过在修改函数中重新解构赋值来获取最新的state值,解决了因数据更新不及时导致的问题。
25 0
|
2月前
|
存储 JavaScript 前端开发
react redux 实现原理
【8月更文挑战第29天】react redux 实现原理
20 4
|
2月前
|
前端开发 Java UED
瞬间变身高手!JSF 与 Ajax 强强联手,打造极致用户体验的富客户端应用,让你的应用焕然一新!
【8月更文挑战第31天】JavaServer Faces (JSF) 是 Java EE 标准的一部分,常用于构建企业级 Web 应用。传统 JSF 应用采用全页面刷新方式,可能影响用户体验。通过集成 Ajax 技术,可以显著提升应用的响应速度和交互性。本文详细介绍如何在 JSF 应用中使用 Ajax 构建富客户端应用,并通过具体示例展示 Ajax 在 JSF 中的应用。首先,确保安装 JDK 和支持 Java EE 的应用服务器(如 Apache Tomcat 或 WildFly)。
33 0
|
2月前
|
存储 JavaScript 前端开发
探索React状态管理:Redux的严格与功能、MobX的简洁与直观、Context API的原生与易用——详细对比及应用案例分析
【8月更文挑战第31天】在React开发中,状态管理对于构建大型应用至关重要。本文将探讨三种主流状态管理方案:Redux、MobX和Context API。Redux采用单一存储模型,提供预测性状态更新;MobX利用装饰器语法,使状态修改更直观;Context API则允许跨组件状态共享,无需第三方库。每种方案各具特色,适用于不同场景,选择合适的工具能让React应用更加高效有序。
43 0
|
2月前
|
存储 前端开发
React 中的 state 和 props 有什么区别?
【8月更文挑战第31天】
32 0

热门文章

最新文章

下一篇
无影云桌面