夜猫子的知识栈 夜猫子的知识栈
首页
  • 前端文章

    • JavaScript
  • 学习笔记

    • 《JavaScript教程》
    • 《Web Api》
    • 《ES6教程》
    • 《Vue》
    • 《React》
    • 《TypeScript》
    • 《Git》
    • 《Uniapp》
    • 小程序笔记
    • 《Electron》
    • JS设计模式总结
  • 《前端架构》

    • 《微前端》
    • 《权限控制》
    • monorepo
  • 全栈项目

    • 任务管理日历
    • 无代码平台
    • 图书管理系统
  • HTML
  • CSS
  • Nodejs
  • Midway
  • Nest
  • MySql
  • 其他
  • 技术文档
  • GitHub技巧
  • 博客搭建
  • Ajax
  • Vite
  • Vitest
  • Nuxt
  • UI库文章
  • Docker
  • 学习
  • 面试
  • 心情杂货
  • 实用技巧
  • 友情链接
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

夜猫子

前端练习生
首页
  • 前端文章

    • JavaScript
  • 学习笔记

    • 《JavaScript教程》
    • 《Web Api》
    • 《ES6教程》
    • 《Vue》
    • 《React》
    • 《TypeScript》
    • 《Git》
    • 《Uniapp》
    • 小程序笔记
    • 《Electron》
    • JS设计模式总结
  • 《前端架构》

    • 《微前端》
    • 《权限控制》
    • monorepo
  • 全栈项目

    • 任务管理日历
    • 无代码平台
    • 图书管理系统
  • HTML
  • CSS
  • Nodejs
  • Midway
  • Nest
  • MySql
  • 其他
  • 技术文档
  • GitHub技巧
  • 博客搭建
  • Ajax
  • Vite
  • Vitest
  • Nuxt
  • UI库文章
  • Docker
  • 学习
  • 面试
  • 心情杂货
  • 实用技巧
  • 友情链接
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • 核心概念

  • 高级指引

  • Hook

  • 案例演示

  • Router

  • Redux

    • 入门Redux
    • Redux基础
    • 数据范式化和性能优化
      • 提升渲染性能
        • 关于 redux 的渲染行为
        • 使用记忆化的 Selector
        • createSelector 函数
      • 范式化数据
        • 什么是范式化?
        • createEntityAdapter
        • getSelectors
        • getInitialState
        • upsertMany
    • Redux Toolkit详细示例
  • Jotai

  • Mobx

  • 其他

  • 《React》笔记
  • Redux
夜猫子
2022-11-28
目录

数据范式化和性能优化

# 提升渲染性能

# 关于 redux 的渲染行为

我们每当发起一个 action 时,都会运行 useSelector,如果返回的是一个新的值,组件将会强制渲染。

如果我们在 useSelector 钩子中使用了 filter() 等函数,将会导致 useSelector 总会返回一个新的数组,那么每次调用 action 时,组件都将重新渲染,无论返回的数据有没有发生变化。

# 使用记忆化的 Selector

要解决上面的问题,我们就需要当数据未改变时获取的是上次数组的引用。

即“记忆”:保存之前的一组输入和计算的结果,如果输入相同,则返回之前的结果,而不是重新计算。

# createSelector 函数

features/posts/postsSlice.js

import { createSlice, createAsyncThunk, createSelector } from '@reduxjs/toolkit'

// omit slice logic

export const selectAllPosts = state => state.posts.posts

export const selectPostById = (state, postId) =>
  state.posts.posts.find(post => post.id === postId)

export const selectPostsByUser = createSelector(
  [selectAllPosts, (state, userId) => userId],
  (posts, userId) => posts.filter(post => post.user === userId)
)
1
2
3
4
5
6
7
8
9
10
11
12
13

createSelector 参数:

  • 将一个或多个“输入 selector ”函数作为第一个参数;

  • 将一个 “输出 selector” 函数作为第二个参数,并且该函数的参数来自于 createSelector 的第一个参数。

我们尝试多次调用 selectPostsByUser,它只会在 posts 或 userId 发生变化时重新执行输出 selector

记忆化的 selector 是提高 React + Redux 应用程序性能的宝贵工具,因为它们可以帮助我们避免不必要的重新渲染,并且如果输入数据没有更改,还可以避免执行潜在的复杂或昂贵的计算。

# 范式化数据

React 的默认行为是当父组件渲染时,React 会递归渲染其中的所有子组件!

所以当重新渲染一个组件时会导致其组件树的重新渲染。

一种方法是使用 React 提供的 memo 来缓存数据:

let PostExcerpt = ({ post }) => {
  // omit logic
}

PostExcerpt = React.memo(PostExcerpt)
1
2
3
4
5

这确保了组件只有在 props 真正更改时才会重新渲染。

另一种方法是使用 redux Toolkit 提供的 createEntityAdapter 函数管理范式化数据。

# 什么是范式化?

我们一般将数据储存在数组中,当想要对数据通过 ID 字段来查找时,不得不使用遍历所有数据来获取 ID 对应项,当数据量较大时就比较耗时了。而一种无需检查所有其他项,直接根据其 ID 查找单个项的方法过程被称为“范式化”。

以下是一组“用户”对象的范式化 state 可能,如下所示:

{
  users: {
    ids: ["user1", "user2", "user3"],
    entities: {
      "user1": {id: "user1", firstName, lastName},
      "user2": {id: "user2", firstName, lastName},
      "user3": {id: "user3", firstName, lastName},
    }
  }
}
1
2
3
4
5
6
7
8
9
10

说明

类似于 map 结构

# createEntityAdapter

createEntityAdapter API 提供了一种将数据储存在 slice 中的标准方法,方法是获取项目集合并将它们放入 { ids: [], entities: {} } 的结构中。除了这个预定义的 state 结构,它还会生成一组知道如何处理该数据的 reducer 函数和 selector。

createEntityAdapter 接受一个选项对象,该对象可能包含一个 sortComparer 函数,该函数将用于通过比较两个项目来保持项目 id 数组的排序(工作方式与 array.sort() 相同)。

import {
  createEntityAdapter
} from '@reduxjs/toolkit'

const postsAdapter = createEntityAdapter({
  // 按照日期排序
  sortComparer: (a, b) => b.date.localeCompare(a.date)
})
1
2
3
4
5
6
7
8

它返回一个对象,该对象包含 一组生成的 reducer 函数,用于操作实体 state 对象。这些 reducer 函数既可以用作特定 action 类型的 reducer,也可以用作 createSlice 中另一个 reducer 中的 "mutating" 实用函数。其中:

# getSelectors

可以传入一个 selector,它从 Redux 根 state 返回这个特定的 state slice,它会生成类似于 selectAll 和 selectById 的选择器。

export const {
  selectAll: selectAllPosts,
  selectById: selectPostById,
  selectIds: selectPostIds
  // Pass in a selector that returns the posts slice of state
} = postsAdapter.getSelectors(state => state.posts)
1
2
3
4
5
6

# getInitialState

生成一个空的 {ids: [], entities: {}} 对象。也可以传递更多字段给 getInitialState,这些字段将会被合并。

const initialState = postsAdapter.getInitialState({
  status: 'idle',
  error: null
})
const postsSlice = createSlice({
  name: 'posts',
  initialState,
    // omit
})
1
2
3
4
5
6
7
8
9

# upsertMany

const postsSlice = createSlice({
  name: 'posts',
  initialState,
	 // omit reducers
  },
  extraReducers: {
    [fetchPosts.fulfilled]: (state, action) => {
      state.status = 'succeeded'
      // Add any fetched posts to the array
      // Use the `upsertMany` reducer as a mutating update utility
      postsAdapter.upsertMany(state, action.payload)
    },
    // Use the `addOne` reducer for the fulfilled case
    [addNewPost.fulfilled]: postsAdapter.addOne
  }
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

当收到 fetchPosts.fulfilled action 时,我们通过将现有 state 和 action.payload 传入 postsAdapter.upsertMany 函数,从而将所有传入的数据添加到 state 中。如果 action.payload 中有任何项目已经存在于我们 state 中,upsertMany 函数将根据匹配的 ID 将它们合并在一起。

总结

  • 可用于优化性能的记忆化 selector 函数
    • Redux Toolkit 重新导出了 Reselect 中的 createSelector 函数,该函数会生成记忆化的 selector
    • 只有当输入 selector 返回新的值时,记忆 selector 才会重新计算结果
    • 记忆化可以跳过昂贵的计算,并确保返回相同的结果引用
  • 可以使用多种方式来优化使用了 Redux 的 React 组件的渲染
    • 避免在 useSelector 中创建新的对象/数组引用——这将导致不必要的重新渲染
    • 可以传递记忆化的 selector 函数给useSelector来优化渲染
    • useSelector 可以接受比较函数,例如 shallowEqual,而不是引用相等
    • 组件可以包装在 React.memo() 中,仅在它们的 prop 发生变化时重新渲染
    • 列表渲染可以通过让列表父组件仅读取每项的 ID 组成的数组、将 ID 传递给列表项子项并在子项中按 ID 检索项来实现优化
  • 范式化 state 结构是存储项的推荐方法
    • “范式化”意味着不重复数据,并通过 ID 将项目存储在查找表中
    • 范式化 state 形式通常看起来像 {ids: [], entities: {}}
  • Redux Toolkit 的 createEntityAdapter API 帮助管理slice中的范式化数据
    • 通过传入 sortComparer 选项,可以按排序顺序保持项目 ID
    • adapter 对象包括:
      • adapter.getInitialState,它可以接受额外的 state 字段,如加载 state
      • 预先创建通用 reducer,例如 setAll、addMany、upsertOne 和 removeMany
      • adapter.getSelectors,生成类似于 selectAll 和 selectById 的 selector
编辑 (opens new window)
上次更新: 2023/7/11 18:57:41
Redux基础
Redux Toolkit详细示例

← Redux基础 Redux Toolkit详细示例→

最近更新
01
IoC 解决了什么痛点问题?
03-10
02
如何调试 Nest 项目
03-10
03
Provider注入对象
03-10
更多文章>
Copyright © 2019-2025 Study | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式