十四、答案

在这里,我们回答每章末尾提出的所有问题。你可以用这些问题来复习你在本书中学到的东西。

第 1 章:介绍 React 和 React 挂钩

  1. React 的三个基本原则是什么
    • 陈述式:我们不是告诉 React 如何做事,而是告诉它我们想要什么。因此,我们可以轻松地设计应用,并在数据更改时高效地更新和呈现正确的组件。
    • 基于组件的:React 封装了管理其自身状态和视图的组件,然后允许我们组合它们以创建复杂的用户界面。
    • 一次学习,随时随地编写:React 不会对您的技术堆栈进行假设,并尝试确保您可以在不重写现有代码的情况下进行开发。
  2. React 中的两种组件是什么?
    • 函数组件:将道具作为参数并返回用户界面的 JavaScript 函数(通常通过 JSX)
    • 类组件:提供render方法的 JavaScript 类,返回用户界面(通常通过 JSX)
  3. React 中的类组件有什么问题?

    • JavaScript 类对于开发人员来说很难理解:this上下文可能令人困惑,有时我们不得不同时在多个地方编写代码
    • 对于机器来说,它们也很难理解:很难判断将调用哪些方法,因此,性能优化实际上是不可能的
  4. 在 React 中使用高阶组件有什么问题?

    • 使用高阶组件将组件引入到视图树中,这些组件实际上与视图结构无关。拥有许多高阶组件会导致所谓的包装器地狱
  5. 我们可以使用哪个工具来设置 React 项目,我们需要运行什么命令来使用它?
    • 我们可以使用create-react-app。要创建新项目,我们必须运行npx create-react-app <app-name>yarn create react-app <app-name>
  6. 如果类组件出现以下错误,我们需要做什么:TypeError:undefined 不是对象(计算'this.setState')
    • 我们忘了在类的constructor中重新绑定方法的this上下文。因此,this不是指向类,而是指向输入字段的上下文。
  7. 我们如何使用挂钩访问和设置 React 状态?
    • 我们使用的useState()挂钩如下:const [ name, setName ] = useState('')
  8. 与类组件相比,使用带挂钩的函数组件有哪些优点?
    • 带有挂钩的函数组件不会遇到与类相同的问题。它们是声明性的,因此更符合 React 的基本原则。挂钩还使我们的代码更简洁,更容易理解。
  9. 更新 React 时是否需要使用挂钩将所有类组件替换为函数组件?

    • 不,我们不需要替换所有类组件。带有挂钩的函数组件可以与现有类组件并排工作,并且是 100%向后兼容的。我们可以简单地使用挂钩编写新组件,或者按照自己的速度升级现有组件。
  10. React 提供的三个基本挂钩是什么?

    • useStateuseEffectuseContext挂钩是 React 提供的基本挂钩,在项目中使用非常频繁。不过,React 还提供了一些更高级的现成挂钩。

第 2 章:使用状态挂钩

  1. 我们在开发自己的useState挂钩的重新实现时遇到了什么问题?我们是如何解决这些问题的?
    • 一个问题是每次呈现组件时都会初始化该值。我们通过使用全局变量存储值来解决这个问题。
    • 然后,我们遇到了多个挂钩写入同一全局变量的问题。为了解决这个问题,我们将值存储在一个数组中,并通过为每个挂钩分配一个索引来跟踪当前挂钩。
  2. 为什么在挂钩的 React 实现中不可能使用条件挂钩?
    • 条件挂钩是不可能的,因为 React 使用挂钩定义的顺序来跟踪值。如果我们以后更改挂钩的顺序,这些值将被分配给不同的挂钩。
  3. 什么是挂钩,它们处理什么?
    • 挂钩是处理 React 应用中的状态和效果的函数
  4. 使用挂钩时,我们需要注意什么?
    • 我们需要确保挂钩的顺序始终保持不变,因此不能在循环或条件中使用挂钩
  5. 挂钩的替代 API 思想的常见问题是什么?
    • 命名挂钩存在名称冲突的问题。每个挂钩都必须有一个唯一的名称,即使在库中使用挂钩也是如此。
    • 挂钩工厂需要更多的样板代码,主要是实例化每个挂钩两次,一次在组件外部,一次在组件内部。此外,它们使创建自定义挂钩变得更加困难。
  6. 我们如何实现条件挂钩?

    • 在简单的情况下,我们总是可以定义挂钩。否则,我们必须拆分组件并有条件地呈现单独的组件,而不是有条件地呈现挂钩。
  7. 我们如何在循环中实现挂钩?

    • 在简单的情况下,我们可以在状态挂钩中存储一个数组。否则,我们必须拆分组件并在循环中渲染单独的组件。

第 3 章:使用 React 挂钩编写第一个应用

  1. React 中文件夹结构的最佳实践是什么?
    • 首先从一个简单的结构开始,在需要时进行更深入的嵌套。启动项目时不要花太多时间考虑文件结构。
  2. 分解 React 组件时应使用哪一原则?
    • 单一责任原则,规定每个组件都应对功能的单个封装部分负责
  3. map函数的作用是什么?
    • map函数将给定函数应用于数组的所有元素,并返回一个包含结果的新数组
  4. 解构是如何工作的,我们什么时候使用它?
    • 通过分解结构,我们可以通过在赋值的左侧指定结构和变量名,从对象或数组元素中获取属性。我们可以使用解构来获得 React 组件中的某些道具。
  5. spread 操作符是如何工作的,我们什么时候使用它?
    • “扩展”操作符在另一个对象/数组的某个点插入对象的所有属性或数组的所有元素。它可用于创建新数组或对象,或将对象的所有属性作为道具传递给 React 组件。
  6. 我们如何使用 React 挂钩处理输入字段?
    • 我们为输入字段值创建一个状态挂钩,并定义一个设置该值的处理函数
  7. 应该在哪里定义本地状态挂钩?

    • 本地状态挂钩应该始终在使用它们的组件中定义
  8. 什么是全球状态?

    • 全局状态是在整个应用中跨多个组件使用的状态
  9. 全局状态挂钩应该定义在哪里?
    • 全局状态挂钩应尽可能在组件树中定义为最高层。在我们的例子中,我们在App组件中定义了它们。

第 4 章:使用简化器和效果挂钩

  1. 状态挂钩的常见问题是什么?
    • 使用状态挂钩很难进行复杂的状态更改
  2. 什么是行动?
    • 动作是描述状态变化的对象,例如,{ type: 'CHANGE_FILTER', byAuthor: 'Daniel Bugl' }
  3. 什么是简化器?
    • 简化器是处理状态变化的函数。它们接受当前状态和动作对象并返回新状态。
  4. 什么时候我们应该使用减速机挂钩而不是状态挂钩?
    • 当需要复杂的状态变化时,应使用简化器挂钩。通常,全局状态就是这样。
    • 当多个状态挂钩的 setter 函数一起调用时,这是使用 Reducer 挂钩的好指标。
  5. 要将状态挂钩转换为简化器挂钩,需要执行哪些步骤?
    • 我们首先需要定义动作,然后是 reducer 函数,最后是 reducer 挂钩
  6. 我们如何更容易地创建动作?
    • 我们可以定义返回动作对象的函数,即所谓的动作创建者
  7. 我们什么时候合并?
    • 当我们想要避免使用两个单独的分派函数,或者当同一个操作修改多个还原器中的状态时
  8. 合并简化器挂钩时需要注意什么?

    • 我们需要确保每个 reducer 返回未处理操作的当前状态
  9. 什么是类组件中的效果挂钩的等价物?

    • 在 React 类组件中,我们将使用componentDidMountcomponentDidUpdate来处理效果
  10. 与类组件相比,使用效果挂钩的优势是什么?
    • 对于效果挂钩,我们不需要同时定义componentDidMountcomponentDidUpdate。此外,效果挂钩更容易理解,我们不需要知道 React 在内部是如何工作的,就可以使用它们。

第 5 章:实现 React 上下文

  1. 上下文可以避免哪些问题?
    • 上下文避免了通过多个层次的组件传递道具
  2. 上下文由哪两部分组成?
    • React 上下文由提供者和使用者组成
  3. 为了使用上下文,是否需要定义这两个部分?
    • 不需要提供程序,因为当未定义提供程序时,上下文将使用传递给React.createContext的默认值
  4. 使用挂钩而不是传统上下文消费者的优势是什么?
    • 挂钩不需要为使用者使用组件和渲染道具。在消费组件中使用多个上下文使我们的组件树非常深,并且我们的应用更难调试和维护。挂钩允许我们通过调用挂钩函数来使用上下文,从而避免了这个问题。
  5. 什么是上下文的替代方案,我们应该在什么时候使用它?
    • 上下文使得重用组件更加困难。只有当我们需要访问不同嵌套级别的多个组件中的数据时,才应该使用上下文。否则,我们可以使用称为控制反转的技术传递道具或渲染组件。
  6. 我们如何实现动态变化的上下文?

    • 我们需要使用状态挂钩为上下文提供程序提供值
  7. 什么时候为状态使用上下文有意义?

    • 通常,为全局状态使用上下文是有意义的,全局状态在不同嵌套级别的多个组件之间使用

第 6 章:执行请求和 ReactSuspence

  1. 如何从一个简单的 JSON 文件轻松创建一个完整的 RESTAPI?

    • 我们可以使用json-server工具从 JSON 文件创建一个完整的 RESTAPI,用于开发和测试
  2. 在开发过程中使用代理访问我们的后端服务器有什么好处?

    • 当使用代理时,我们不需要在开发过程中处理跨站点的限制
  3. 我们可以使用哪些挂钩组合来实现请求?
    • 我们可以使用 Effect 和 State 或 Reducer 挂钩来实现请求
  4. 我们可以使用哪些库来实现请求?
    • 我们还可以使用axiosreact-request-hook库来实现请求
  5. 我们如何使用react-request-hook处理加载状态?
    • 我们可以使用从useResource挂钩返回的result.isLoading标志,有条件地显示加载消息
  6. 我们如何使用react-request-hook处理错误?
    • 我们可以使用从useResource挂钩返回的result.error对象并分派错误操作
  7. 如何防止不必要的组件重新渲染?
    • 使用React.memo可以防止不必要的重新渲染,类似于shouldComponentUpdate
  8. 我们如何减少应用的捆绑大小?
    • 我们可以使用React.Suspense延迟加载某些组件,这意味着只有在需要时才会从服务器请求它们

第 7 章:使用挂钩进行路由

  1. 为什么我们需要定义单独的页面?
    • 大多数大型应用由多个页面组成。例如,每个博客文章都有一个单独的页面
  2. 如何使用 Navi 库定义管线?
    • 我们使用mount函数并将对象映射路径传递给route函数
  3. 我们如何使用 URL 参数定义路由?

    • 我们可以使用:parameter语法在路径中指定 URL 参数
  4. 如何使用 Navi 定义静态链接?

    • 可以使用react-navi中的Link组件定义静态链路
  5. 我们如何实现动态导航?
    • 使用useNavigation挂钩并调用navigation.navigate()可以实现动态导航
  6. 哪个挂钩用于访问当前路由的路由信息?
    • useCurrentRoute挂钩向我们提供了当前路线的所有信息
  7. 哪个挂钩用于访问当前装载路线的路线信息?
    • useLoadingRoute挂钩向我们提供了当前正在加载的路线的所有信息

第 8 章:使用社区挂钩

  1. 我们可以使用哪个挂钩来简化输入字段处理?
    • 我们可以使用react-hookedup库中的useInput挂钩
  2. 如何使用效果挂钩实现componentDidMountcomponentWillUnmount生命周期?

    • componentDidMount可以通过使用带有作为第二个参数传递的空数组的效果挂钩来实现。例如,useEffect(() => console.log('did mount'), [])
  3. componentWillUnmount可以通过从一个 Effect Hook 返回一个函数来实现,其中一个空数组作为第二个参数传递,例如useEffect(() => { return () => console.log('will unmount') }, [])

  4. 我们如何使用挂钩来获取this.setState()的行为?

    • this.setState()将现有状态对象与给定状态对象合并。我们可以通过使用useMergeState挂钩而不是简单的状态挂钩来获得相同的行为。
  5. 为什么我们应该使用计时器挂钩而不是直接调用setTimeoutsetInterval

    • 当定义简单的超时或间隔时,它们将在组件重新渲染时重置。为了防止这种重置发生,我们必须使用来自react-hookedupuseTimeoutuseInterval挂钩。
  6. 我们可以使用哪些挂钩来简化处理公共数据结构?

    • 我们可以使用react-hookedup中的useBooleanuseArrayuseCounter挂钩
  7. 什么时候我们应该使用带有挂钩的响应式设计,而不是简单地使用 CSS 媒体查询?
    • 当呈现画布或 WebGL 中的元素时,或者当我们需要根据窗口大小动态决定是否加载组件时,我们应该使用挂钩进行响应性设计
  8. 我们可以使用哪个挂钩实现撤销/重做功能?
    • 我们可以使用use-undo库中的useUndo挂钩在我们的应用中实现简单的撤销/重做功能
  9. 什么是去 Bouncing?我们为什么要这样做?
    • 去 Bouncing 意味着函数只会在一定时间后被调用,而不是每次事件触发它时。使用去 Bouncing,我们可以仅在每秒之后,而不是在每个键入的字符之后,在撤消历史记录的文本字段中存储输入的值。
  10. 我们可以用哪个挂钩去弹跳?
    • 我们可以使用use-debounce库中的useDebounceuseDebouncedCallback挂钩

第 9 章:挂钩规则

  1. 哪里可以叫挂钩?
    • 挂钩只能在 React 函数组件或自定义挂钩的开头调用
  2. 我们可以在 React 类组件中使用挂钩吗?
    • 不,不可能在 React 类组件中使用挂钩
  3. 关于挂钩的顺序,我们需要注意什么?
    • 挂钩的顺序永远不应该改变,因为它用于跟踪各种挂钩的值
  4. 挂钩可以在条件、循环或嵌套函数中调用吗?
    • 不,不能在条件、循环或嵌套函数内调用挂钩,因为这会改变挂钩的顺序
  5. 挂钩的命名约定是什么?

    • 挂钩函数名称应始终以use前缀开头,然后在CamelCase中添加名称。例如:useSomeHookName
  6. 我们如何能够自动执行挂钩规则?

    • 我们可以使用eslinteslint-plugin-react-hooks来执行挂钩规则
  7. 什么是彻底依赖规则?
    • 穷举依赖项规则确保在一个挂钩中,所有使用的变量都通过第二个参数作为依赖项列出
  8. 我们如何自动修复过梁警告?
    • 我们可以运行npm run lint -- --fix命令来自动修复 linter 警告。例如,运行此命令将自动输入 Effect 挂钩中使用的所有变量作为依赖项。

第十章:构建自己的挂钩

  1. 如何从现有代码中提取自定义挂钩?

    • 我们可以简单地将代码放入一个单独的函数中。在自定义挂钩函数中,可以使用其他挂钩函数,但我们需要确保不违反挂钩规则。
  2. 创建 API 挂钩的优势是什么?

    • 当为各种 API 调用定义单独的函数时,如果 API 以后发生更改,我们可以轻松地调整它们,因为我们将所有与 API 相关的代码放在一个地方
  3. 什么时候应该将功能提取到自定义挂钩中?
    • 当某个功能在多个地方使用或者以后可以重复使用时,我们应该创建一个自定义挂钩
  4. 我们如何使用定制挂钩?
    • 我们可以简单地调用定制挂钩,就像调用官方的 React 挂钩或库中的挂钩一样
  5. 我们应该什么时候创建本地挂钩?
    • 当我们想将某个功能封装在一个单独的函数中时,可以使用局部挂钩,但它只能在单个组件中使用
  6. 挂钩之间的哪些交互是可能的?

    • 我们可以在挂钩函数中使用其他挂钩,并且可以将值从其他挂钩传递给挂钩
  7. 我们可以使用哪个库来测试挂钩?

    • 我们可以使用jest测试转轮结合 React Hooks 测试库(@testing-library/react-hooksreact-test-renderer来测试挂钩
  8. 我们如何测试挂钩动作?
    • 可以使用act功能测试吊钩动作。例如,act(() => result.current.increment())
  9. 我们如何测试上下文?
    • 可以通过编写返回提供程序的上下文包装函数来测试上下文。然后可以将包装器函数传递给renderHook函数。例如,const { result } = renderHook(() => useTheme(), { wrapper: ThemeContextWrapper })
  10. 我们如何测试异步代码?
    • 我们可以将 async/await 构造与renderHook返回的waitForNextUpdate函数结合使用,等待异步代码完成运行

第 11 章:从 React 类组件迁移

  1. React 类组件是如何定义的?
    • 使用class ComponentName extends React.Component {定义 React 类组件
  2. 在类组件中使用constructor时,我们需要调用什么?为什么?
    • 我们首先需要调用super(props)以确保道具传递给React.Component
  3. 如何设置类组件的初始状态?

    • 我们可以通过在constructor中定义this.state对象来设置类组件中的初始状态
  4. 如何更改类组件的状态?

    • 在类组件中,我们使用this.setState()来更改状态
  5. 为什么我们需要用类组件方法重新绑定this上下文?
    • 当将方法作为事件处理程序传递给元素时,this上下文将更改为触发事件的元素。我们需要将this上下文重新绑定到类以防止这种情况发生。
  6. 我们如何重新绑定this上下文?
    • 我们需要在构造函数中的方法上使用.bind(this)。例如,this.handleInput = this.handleInput.bind(this)
  7. 如何将 React 上下文用于类组件?
    • 我们可以设置contextType,然后访问this.context。例如,static contextType = StateContext
    • 如果我们想使用多个上下文,我们可以使用上下文消费者。例如,<StateContext.Consumer>{value => <div>State is: {value}</div>}</StateContext.Consumer>
  8. 在迁移到挂钩时,我们可以用什么替换状态管理?

    • 我们可以用状态挂钩替换this.statethis.setState
  9. 使用挂钩和类组件的权衡是什么?

    • 带有挂钩的函数组件更简单(不需要处理构造函数,this或多次分解相同的值,在处理上下文、道具和状态时没有魔力,不需要同时定义componentDidMountcomponentDidUpdate。功能组件还鼓励制作小而简单的组件,更易于重构和测试,需要的代码更少,初学者更容易理解,并且更具声明性。
    • 然而,当遵守某些约定并使用最新的 JavaScript 特性以避免this重新绑定时,类组件可以很好地工作。此外,由于现有的知识,类组件对于团队来说可能更容易理解。
  10. 应在何时以及如何将现有项目迁移到挂钩?
    • 适当时,用基于挂钩的函数组件慢慢替换旧类组件。例如,当您已经在重构组件时。

第 12 章:重复使用和挂钩

  1. Redux 应该用于什么样的状态?
    • Redux 应该用于全局状态,全局状态是应用中多个组件中使用的状态
  2. Redux 由哪些元素组成?
    • Redux 由存储(描述应用完整状态的对象)、动作(描述状态修改的对象)、动作创建者(创建动作对象的函数)、还原器组成(获取当前状态和动作对象并返回新状态的函数)和连接器(将现有组件连接到 Redux 的高阶组件)
  3. Redux 的三个原则是什么?

    • 单一真实来源(数据应始终具有单一来源)
    • 只读状态(不能直接修改状态,只能通过分派操作)
    • 使用纯函数处理状态更改(给定相同的状态和操作,还原程序将始终返回相同的新状态)
  4. 为什么我们要定义动作类型?

    • 动作类型在定义或比较动作的type属性时避免输入错误
  5. 我们如何将组件连接到 Redux?
    • 我们可以使用connect高阶组件,也可以使用分派和选择器挂钩
  6. 我们可以在 Redux 中使用哪些挂钩?
    • useDispatch获取调度功能,useSelector获取某一部分状态,useStore获取 Redux 存储(针对特殊用例,如更换简化器)
  7. 我们为什么要创建可重用的选择器?
    • 可重用选择器可用于多个组件中。此外,它们会将结果存储起来,并仅在状态更改时重新计算。
  8. 我们如何迁移 Redux 应用?

    • 我们应该首先用状态挂钩替换简单的局部状态,例如输入字段值。然后用简化器挂钩替换复杂的局部状态。我们将跨多个组件使用的全局状态保存在 Redux 存储中。最后,我们使用选择器和分派挂钩代替connect高阶组件。
  9. Redux 的权衡是什么?

    • 使用 Redux 的优点是:它提供了一个特定的项目结构,允许我们以后轻松地扩展和修改代码,代码中出现错误的可能性更小,它比简单地使用 React 上下文来表示状态有更好的性能,并且它使我们的App通过将状态管理和操作创建者转移到 Redux,组件变得更加简单
    • 使用 Redux 的缺点是:它需要大量样板代码,项目结构变得更加复杂,并且需要一个包装器组件(Provider)将应用连接到应用商店
  10. 我们什么时候应该使用 Redux?
    • 我们应该只对需要复杂状态更改的应用使用 Redux。对于简单的项目,Reducer 挂钩甚至仅仅是 State 挂钩就足够了。

第 13 章:MobX 和挂钩

  1. 哪些元素构成 MobX 生命周期?
    • 事件调用动作,修改状态。状态是可观察的,不应包含冗余或可派生的数据。计算值通过纯函数从状态中导出。React类似于计算值,但它们也会产生副作用,例如在 React 中更新用户界面。
  2. MobX 提供哪些装饰器?
    • MobX 为各种元素提供装饰器:observerobservablecomputedaction
  3. 我们如何将组件连接到 MobX?
    • 我们可以使用Provider组件将我们的应用连接到 MobX 商店,然后通过inject高阶组件连接组件。如果我们想让一个组件在状态改变时自动重新渲染,我们还需要用observer装饰函数包装它。
  4. MobX 提供哪些挂钩?

    • 我们可以使用useObserver挂钩来定义组件的某些部分,当状态发生变化时,这些部分应该重新计算
  5. 我们如何使用 hook 访问 MobX 商店?

    • MobX 提供了一个上下文,可用于创建访问 MobX 存储的自定义挂钩。我们可以使用普通的上下文挂钩从mobx-react访问MobXProviderContext
  6. 我们可以使用 MobX 存储本地状态吗?
    • 是的,使用 MobX,我们可以创建任意数量的商店。MobX 甚至提供了一个useLocalStore挂钩来创建本地商店。
  7. 我们应该如何将现有的 MobX 应用迁移到 hook?

    • 我们可以慢慢地升级 MobX 应用的某些部分。我们可以使用一个自定义挂钩来访问上下文的一部分,而不是inject高阶组件。我们可以使用useObserver挂钩来代替observer高阶分量。
    • 我们应该首先对简单的局部状态使用状态挂钩,然后对复杂的局部状态使用useLocalState挂钩,最后将全局状态保存在单独的 MobX 存储中。
  8. 使用 MobX 的优点是什么?

    • 它提供了一种处理状态更改的简单方法,需要更少的样板代码,在应用代码的结构上提供了更大的灵活性,允许使用多个全局和本地存储,并且通过将状态管理和操作卸载到 MobX,使App组件更加简单
  9. 使用 MobX 的缺点是什么?
    • 它允许状态变化发生在任何地方,而不仅仅是在单个商店,这可能会使我们的应用更加不可预测。更大的灵活性也意味着可能以一种糟糕的方式构建项目,并导致错误或 bug。此外,如果我们想获得所有功能,MobX 需要一个包装器组件将应用连接到应用商店(我们可以直接导入并使用 MobX 应用商店,但它会破坏服务器端渲染等功能)。
  10. 什么时候不应该使用 MobX?
    • 如果状态更改很简单,并且只使用组件中的本地状态,则不应使用 MobX。在这种情况下,State 和 Reducer 挂钩就足够了。