疫情期间宅在家学习咯!本文是极客时间《React实战进阶45讲》的学习笔记,访问文末了解更多可以查看带连接版笔记
1.理解Store、Action、Reducer
![3ca51f2199cde4e9a48368164891e5f9.png](https://i-blog.csdnimg.cn/blog_migrate/87c5555787011b255a7bfeb6194c1987.jpeg)
1.1 Action
Action 是把数据从应用传到 store 的有效载荷。它是 store 数据的唯一来源。一般来说你会通过 store.dispatch() 将 action 传到 store。 Action 本质上是 JavaScript 普通对象。我们约定,action 内必须使用一个字符串类型的 type 字段来表示将要执行的动作。
const ADD_TODO = 'ADD_TODO'{ type: ADD_TODO, text: 'Build my first Redux app'}
多数情况下,type 会被定义成字符串常量。当应用规模越来越大时,建议使用单独的模块或文件来存放 action。
import { ADD_TODO, REMOVE_TODO } from '../actionTypes'
除了 type 字段外,action 对象的结构完全由你自己决定。
1.2 Reducer
Reducers 指定了应用状态的变化如何响应 actions 并发送到 store 的,记住 actions 只是描述了有事情发生了这一事实,并没有描述应用如何更新 state。
一个 Redux reducer 函数需要具备:
- 应该有类似 (previousState, action) => newState 特征的函数,函数的类型与 Array.prototype.reduce(reducer, ?initialValue) 这个函数很相似。
- 应该是"纯"函数,纯函数意味着不能突变它的参数,如果在函数中执行 API 调用,或者在函数外部修改值,又或者调用一个非纯函数比如 Date.now() 或 Math.random(),那么就会带来一些副作用。这意味着 state 的更新应该在"不可变(immutable)"的理念下完成,这就是说总是去返回一个新的更新后的对象,而不是直接去修改原始的 state tree。
2.combineReducers 工具函数
Reducer就是一个普通的JavaScript函数,如果系统中有多个Reducer的话就可以使用combineReducers来进行组合使用。比如下面的系统有两个Reducer,一个todos,一个counter
![106e1e83c3380b992c676f71b2d51aa7.png](https://i-blog.csdnimg.cn/blog_migrate/120e92165cb15dad86a729394db95969.jpeg)
![efa9a20a041995aed0b6f829f05a2754.png](https://i-blog.csdnimg.cn/blog_migrate/ce33cc387a40af8110703f3750a45183.jpeg)
那么通过combineReducers组合来使用,这个函数接收多个Reducer作为参数,最终形成的是一个封装过后的函数,这个封装后的函数也是一个Reducer。
![7c8edf776571c2e99709c461a6fc22cc.png](https://i-blog.csdnimg.cn/blog_migrate/9064e0d837a0af761d2801feb05b6173.jpeg)
3.理解异步Action、Redux中间件
异步Action
![c2e97375bac512ee2f8fd2b0862f3c24.png](https://i-blog.csdnimg.cn/blog_migrate/29a4fd9a8bde183d27f3112a32db558e.jpeg)
Redux中间件
- 截获Action
- 发出Action
异步Action不是特殊的Action,而是多个同步Action的组合使用,中间件在 dispatch 中截获 Action 做特殊处理。
4.如何组织Action和Reducer
“标准”形式的 Redux Action 的问题
- 所有Action放在一个文件,会无限扩展
- Action、Reducer分开,实现业务逻辑时需要来回切换
- 系统中有哪些Action不够直观
新的方式:单个 Action 和 Reducer 放在同一个文件
![5e0f51acd57dc793f991c776e7e50255.png](https://i-blog.csdnimg.cn/blog_migrate/ad820cf5d744fd193e5f74cd6caa8c2e.jpeg)
这种方式并不是对Redux API做一个封装,而是对代码的存放进行一个重新的组织
![db0b968f323c0e189b408ce82e37f4ae.png](https://i-blog.csdnimg.cn/blog_migrate/f637b8d3774344b7c5ddaaa1d49d2c22.jpeg)
好处:
- 易于开发:不用在 action 和 reducer 文件间来回切换
- 易于维护:每个 action 文件都非常小,容易理解
- 易于测试:每个业务逻辑只需对应一个测试文件
- 易于理解:文件名就是 action 名字,文件列表就是 action 列表
5.理解不可变数据
5.1 不可变数据
其实就是:你不可以直接去修改它的值,而是要通过复制它的值并产生一个新对象的方式来的得到一个新的数据,它包含你要修改的部分。
![a6ef7ed87f12837e8e969267a5e788ba.png](https://i-blog.csdnimg.cn/blog_migrate/64222c43c466b4797e4810efe4fdee5e.jpeg)
5.2 为什么需要不可变数据
不可变数据时Redux运行的基础(为什么需要不可变数据):
- 性能优化。
- 易于调试和跟踪
- 易于推测
性能优化:当一个store发生变化的时候,我们需要通知所有的组件需要更新,所有的store的变化都是由action触发,action触发在原来的旧的state上并产生一个新的state,新的state已经不是原来的state了,它们时两个完全不同的对象,所以,当新的state和旧的state不是同一个对象的时候我们就知道store发生了变化,我们不需要比较其中的值有没有发生变化,我们只需要比较两个引用的状态是不是一样,也就是我们只要判断页面的一个节点的前一个状态和后一个状态的引用是否相等我们就知道当前的store有没有变化,从而决定是否要更新一个组件。也就是说我们不需要去深层次的遍历每个值是否相等,而只要通过引用比较就可以达到这个目的,所以就能达到性能优化的目的。
易于调试和跟踪:当你的store发生变化的时候,任何时刻你都可以去记录之前的状态和之后的状态并且能得到他们 diff 之后的结果,这样在调试的时候你就可以看到之前的状态和之后的状态。
易于推测:在任何时刻你都可以推测到你的store由什么引起的变化,store一定是由action触发的,action之前的状态是什么,之后的状态是什么,通过这个值的比较很容的去判断当前的action是否被正确的处理。
5.3 如何操作不可变数据
操原生写法:{...},object.assign(),大部分情况下的推荐写法
![611085afd6791aa2facafda791455f7f.png](https://i-blog.csdnimg.cn/blog_migrate/faea9820aee4d6f34212fd0a6340730b.jpeg)
immutability-helper,针对复杂一点的情况下,比如我们要更新的节点层次比较深,它引入了新的语法
![038f5e5831ee8da769be89ed4a484ed8.png](https://i-blog.csdnimg.cn/blog_migrate/3fe0b2462803cd661af8f491d708828e.jpeg)
immer:新出现的一个库,它提供了一种非常惊艳的语法,让你操作一个不可变数据的时候就像在直接修改它一样
![a4a08361cbf1b2bac4f22c764b058f97.png](https://i-blog.csdnimg.cn/blog_migrate/775b96015eee3954990a8d7b1c65884d.jpeg)
6.React Router
6.1 路由不只是页面切换,更是代码组织方式
为什么需要路由
- 单页应用需要进行页面切换
- 通过 URL 可以定位到页面
- 更有语义的组织资源
路由实现的基本架构
![f8f20efea319a8fb406ee78d51e6f5a4.png](https://i-blog.csdnimg.cn/blog_migrate/905c72768a583e0019bdde7ab2071e36.jpeg)
React Router 的特性
- 申明式路由定义
- 动态路由
三种路由实现方式
- URL 路径
- hash 路由
- 内存路由,路由并不会反应到URL上,在做服务器端渲染的时候会有用。
import {MemoryRouter} from 'react-router
基于路由配置进行资源组织
- 实现业务逻辑的松耦合
- 易于扩展,重构和维护
- 路由层面实现 Lazy Load
6.2 React Router 核心 API
:普通链接,不会触发浏览器刷新
![1da8e905b4bc89abe063614dea51f642.png](https://i-blog.csdnimg.cn/blog_migrate/8a18df8e2c71e4ba43afe6e2ad7067c6.jpeg)
:类似 Link 但不会添加当前选中状态
![fc2be047171261ec77e2109a2f1bdf44.png](https://i-blog.csdnimg.cn/blog_migrate/eb773a007842113d73d957d3979b5cef.jpeg)
:满足条件时提示用户是否离开当前页面
![8cdd056884a98e9301296f77e7752a41.png](https://i-blog.csdnimg.cn/blog_migrate/4ff5927bfface3336b4baf0bb8dfa2e6.jpeg)
:重定向当前页面,例如登录判断
![7329173b3d105bf9dd193976bd2d2a59.png](https://i-blog.csdnimg.cn/blog_migrate/6926fa28a1f917314b6eb71a4a3e7307.jpeg)
:路由配置的核心标记,路径匹配时显示对应组件
![f189f028e8de1c96da525e6a663a72b4.png](https://i-blog.csdnimg.cn/blog_migrate/e7022a0ba7ee656a763f0b7f85c3b9b6.jpeg)
:只显示第一个匹配的路由
![77fbcbcd50f7607cd0598eea88e6be23.png](https://i-blog.csdnimg.cn/blog_migrate/359bde156f4af8eb2b18935fa222f30d.jpeg)
6.2 参数定义,嵌套路由的使用场景
通过URl传递参数
- 如何通过 URL 传递参数:
- 如何获取参数:this.props.match.params
- https://github.com/pillarjs/path-to-regexp,可以把一个 URL 路径转化为一个正则表达式
7.使用Next.js创建React同构应用
什么时同构应用?
![a635171afe7b6e4180851c46e489aa0f.png](https://i-blog.csdnimg.cn/blog_migrate/e009336ea7b316359eb663f2ab8308a1.jpeg)
创建Next.js项目的三个约定:
- 页面就是 pages 目录下的一个组件
- static 目录映射静态文件
- page 具有特殊静态方法 getInitialProps
8.单元测试
React 让前端单元测试变的容易:
- React 应用很少需要访问浏览器API
- 虚拟 DOM 可以在 NodeJS 环境下运行和测试
- Redux 隔离了状态管理,纯数据层单元测试
单元测试涉及的工具:
- Jest:Facebook 开源的 JS 单元测试框架
- jsdom:浏览器环境的 NodeJS 模拟
- Enzyme:React 组件渲染和测试
- nock:模拟 HTTP 请求
- sinon:函数模拟和调用跟踪
- istanbul:单元测试覆盖率
如果对你有一点点帮助,可以点个关注。
作者简介:做工程不做码农(微信公众号同名),Web前端工程师,7年开发经验,坐标杭州,聚焦大前端技术和程序员成长的公众号。