五、实现 React 上下文

在前面的章节中,我们学习了最基本的挂钩,如状态挂钩、减速机挂钩和效果挂钩。我们使用这些挂钩开发了一个小型博客应用。我们在开发博客应用的过程中注意到,我们需要将user状态从App组件传递到UserBar组件,从UserBar组件传递到LoginRegisterLogout组件。为了避免像这样传递状态,我们现在将学习 React 上下文和上下文挂钩。

我们将从学习什么是 React 上下文,以及什么是提供者和消费者开始。然后,我们将使用上下文挂钩作为上下文使用者,并讨论何时应该使用上下文。最后,我们将通过上下文实现主题和全局状态。

本章将介绍以下主题:

  • 引入 React 上下文作为传递道具的替代方案
  • 通过上下文实现主题
  • 对全局状态使用上下文

技术要求

应该已经安装了 Node.js 的最新版本(v11.12.0 或更高版本)。Node.js 的npm包管理器也需要安装。

本章的代码可以在 GitHub 存储库中找到:hhttps://github.com/PacktPublishing/Learn-React-Hooks/tree/master/Chapter05

请查看以下视频以查看代码的运行情况:

http://bit.ly/2Mm9yoC

Please note that it is highly recommended that you write the code on your own. Do not simply run the code examples that have been provided. It is important that you write the code yourself in order for you to be able to learn and understand properly. However, if you run into any issues, you can always refer to the code example.

现在,让我们从这一章开始。

引入 React 上下文

在前面的章节中,我们将user状态和dispatch函数从App组件传递到UserBar组件;然后从UserBar组件到LogoutLoginRegister组件。React context 通过允许我们在组件之间共享值,而不必通过道具显式地传递道具,为这种在多个组件级别上传递道具的麻烦方式提供了解决方案。正如我们将要看到的,React 上下文非常适合在整个应用中共享值。

首先,我们将仔细研究传递道具的问题。然后,我们将引入 React 上下文作为问题的解决方案。

传递道具

在深入学习 React context 之前,让我们回顾一下我们在前面几章中实现的内容,以便了解 context 解决的问题:

  1. src/App.js中,我们定义了user状态和dispatch功能:
    const [ state, dispatch ] = useReducer(appReducer, { user: '', posts: defaultPosts })
    const { user, posts } = state
  1. 然后,我们将user状态和dispatch函数传递给UserBar组件(和CreatePost组件):
    return (
        <div style={{ padding: 8 }}>
            <UserBar user={user} dispatch={dispatch} />
            <br />
            {user && <CreatePost user={user} posts={posts} dispatch={dispatch} />}
            <br />
            <hr />
            <PostList posts={posts} />
        </div>
    )
  1. src/user/UserBar.js组件中,我们将user状态作为道具,然后将其传递给Logout组件。我们还将dispatch功能作为道具,并将其传递给LogoutLoginRegister组件:
export default function UserBar ({ user, dispatch }) {
    if (user) {
        return <Logout user={user} dispatch={dispatch} />
    } else {
        return (
            <React.Fragment>
                <Login dispatch={dispatch} />
                <Register dispatch={dispatch} />
            </React.Fragment>
        )
    }
}
  1. 最后,我们在LogoutLoginRegister组件中使用了dispatchuser道具。

React 上下文允许我们跳过步骤 2 和 3,直接从步骤 1 跳到步骤 4。正如你们所想象的,对于更大的应用,上下文变得更加有用,因为我们可能需要在多个层次上传递道具。

引入 React 上下文

React 上下文用于跨 React 组件树共享值。通常,我们希望共享全局值,例如user状态和dispatch函数、我们应用的主题或所选语言。

React 上下文由两部分组成:

  • 提供程序,提供(设置)值
  • 消费者,消费(使用)价值

我们首先将使用一个简单的示例来了解上下文是如何工作的,在下一节中,我们将在我们的博客应用中实现它们。我们使用create-react-app工具创建了一个新项目。在我们的简单示例中,我们将定义一个主题上下文,其中包含应用的主颜色。

定义上下文

首先,我们必须定义上下文。自从引入挂钩以来,这种工作方式没有改变。

我们只需使用React.createContext(defaultValue)函数创建一个新的上下文对象。我们将默认值设置为{ primaryColor: 'deepskyblue' },因此当没有定义提供者时,我们的默认原色将为'deepskyblue'

src/App.js中,在App功能之前添加以下定义:

export const ThemeContext = React.createContext({ primaryColor: 'deepskyblue' })

Note how we are exporting ThemeContext here, because we are going to need to import it for the consumer.

这就是用 React 定义上下文所需要做的一切。现在我们只需要定义消费者。

定义消费者

现在,我们必须在Header组件中定义消费者。目前,我们将以传统方式进行此操作,并在接下来的步骤中使用挂钩来定义使用者:

  1. 创建一个新的src/Header.js文件
  2. 首先,我们需要从App.js文件中导入ThemeContext
import React from 'react'
import { ThemeContext } from './App'
  1. 现在,我们可以定义我们的组件,其中我们使用ThemeContext.Consumer组件和render函数作为children属性,以利用上下文值:
const Header = ({ text }) => (
    <ThemeContext.Consumer>
        {theme => (
  1. render函数中,我们现在可以利用上下文值来设置Header组件的color样式:
            <h1 style={{ color: theme.primaryColor }}>{text}</h1>
        )}
    </ThemeContext.Consumer>
)

export default Header
  1. 现在我们还需要导入src/App.js中的Header组件,增加以下import语句:
import Header from './Header'
  1. 然后,我们将当前的App功能替换为以下代码:
const App = () => (
    <Header text="Hello World" />
)

export default App

使用这样的上下文是可行的,但是,正如我们在第一章中所学到的,以这种方式使用带有render功能道具的组件会弄乱我们的 UI 树,使我们的应用更难调试和维护。

使用挂钩

使用上下文的更好方法是使用useContext挂钩!通过这种方式,我们可以像使用任何其他值一样使用上下文值,就像使用useState挂钩一样:

  1. 编辑src/Header.js。首先,我们从 React 导入useContext挂钩,从src/App.js导入ThemeContext对象:
import React, { useContext } from 'react'
import { ThemeContext } from './App'
  1. 然后,我们创建Header组件,现在我们在其中定义useContext挂钩:
const Header = ({ text }) => {
 const theme = useContext(ThemeContext)
  1. 我们的其他组件将与以前相同,只是现在我们可以简单地返回我们的Header组件,而无需为消费者使用额外的组件:
    return <h1 style={{ color: theme.primaryColor }}>{text}</h1>
}

export default Header

正如我们所看到的,使用挂钩可以使上下文使用者代码更加简洁。此外,它将更易于阅读、维护和调试。

我们可以看到标题现在有颜色deepskyblue

A simple app with a Context Hook!

正如我们所看到的,我们的主题上下文成功地为标题提供了主题。

定义提供者

当没有定义提供程序时,上下文使用传递给React.createContext的默认值。这对于调试未嵌入到应用中的组件非常有用。例如,我们可以将单个组件作为独立组件进行调试。在应用中,我们通常希望使用提供者为上下文提供值,我们现在要定义上下文。

编辑src/App.js,在我们的App函数中,我们简单地用<ThemeContext.Provider>组件包装Header组件,其中我们将coral传递为primaryColor

const App = () => (
    <ThemeContext.Provider value={{ primaryColor: 'coral' }}>
        <Header text="Hello World" />
    </ThemeContext.Provider>
)

export default App

我们现在可以看到我们的标题颜色从deepskyblue变为coral

Our provider changed the color of the header

如果我们想更改上下文的值,我们可以简单地调整传递给Provider组件的value属性。

Please note that the default value of a context is not used when we define a provider without passing the value prop to it! If we define a provider without a value prop, then the value of the context will be undefined.

现在我们已经为上下文定义了一个提供程序,让我们继续定义多个嵌套的提供程序。

嵌套提供程序

使用 React context,还可以为同一上下文定义多个提供者。使用这种技术,我们可以覆盖应用某些部分中的上下文值。让我们考虑前面的例子,并向它添加第二个标题:

  1. 编辑src/App.js,并添加第二个Header组件:
const App = () => (
    <ThemeContext.Provider value={{ primaryColor: 'coral' }}>
        <Header text="Hello World" />
 <Header text="This is a test" />
    </ThemeContext.Provider>
)

export default App
  1. 现在,用不同的primaryColor定义第二个Provider组件:
const App = () => (
    <ThemeContext.Provider value={{ primaryColor: 'coral' }}>
        <Header text="Hello World" />
 <ThemeContext.Provider value={{ primaryColor: 'deepskyblue' }}> <Header text="This is a test" />
        </ThemeContext.Provider>
    </ThemeContext.Provider>
)

export default App

如果在浏览器中打开应用,则第二个标题的颜色与第一个标题的颜色不同:

Overriding context values with nested providers

正如我们所看到的,我们可以通过定义提供者来覆盖 React 上下文值。提供程序也可以嵌套,因此覆盖组件树中较高的其他提供程序的值。

示例代码

小主题上下文示例的示例代码可以在Chapter05/chapter5_1文件夹中找到。

只需运行npm install即可安装所有依赖项,npm start即可启动应用;然后在浏览器中访问http://localhost:3000(如果它没有自动打开)。

上下文的替代

但是,我们应该小心,不要太频繁地使用 React 上下文,因为这会使重用组件更加困难。我们应该只在需要访问位于不同嵌套级别的许多组件中的数据时使用上下文。此外,我们需要确保只对不经常变化的数据使用上下文。频繁更改上下文的值可能会导致整个组件树重新渲染,从而导致性能问题。这就是为什么,对于频繁变化的值,我们应该使用状态管理解决方案,例如 Redux 或 MobX。

如果我们只想避免传递道具,我们可以传递渲染组件而不是数据。例如,假设我们有一个Page组件,它呈现Header组件,它呈现Profile组件,然后它呈现Avatar组件。我们将一个headerSize道具传递给Page组件,这是Header组件中需要的,也是Avatar组件中需要的。我们可以执行以下操作,而不是通过多个关卡传递道具:

function Page ({ headerSize }) {
    const profile = (
        <Profile>
            <Avatar size={headerSize} />
        </Profile>
    )
    return <Header size={headerSize} profile={profile} />
}

现在,只有Page组件需要知道headerSize道具,无需在树中进一步传递。在这种情况下,上下文是不必要的。

这种模式被称为控制反转,它可以使您的代码比传递道具或使用上下文更简洁。但是,我们也不应该总是使用这种模式,因为它使更高级别的组件更加复杂。

实施主题

在一个小示例中学习了如何实现主题之后,我们现在将使用 React 上下文和挂钩在我们的博客应用中实现主题。

定义上下文

首先,我们必须定义上下文。在我们的博客应用中,我们将为上下文创建一个单独的文件,而不是在src/App.js文件中定义它。有一个单独的上下文文件可以使以后更容易维护它们。此外,我们总是知道从何处导入上下文,因为从文件名可以清楚地看到。

让我们开始定义一个主题上下文:

  1. 创建一个新的src/contexts.js文件。
  2. 然后,我们导入React
import React from 'react'
  1. 接下来,我们定义ThemeContext。与前面的小示例一样,我们将默认值primaryColor设置为deepskyblue。此外,我们将secondaryColor设置为coral
export const ThemeContext = React.createContext({
    primaryColor: 'deepskyblue',
    secondaryColor: 'coral'
})

现在我们已经定义了上下文,我们可以继续定义上下文挂钩。

定义上下文挂钩

在定义了上下文之后,我们将使用上下文挂钩来定义我们的消费者。我们首先为头部创建一个新组件,然后为现有的Post组件定义一个上下文挂钩。

创建标题组件

首先,我们创建一个新的Header组件,它将在我们应用的primaryColor中显示React Hooks Blog

现在让我们创建Header组件:

  1. 创建一个新的src/Header.js文件。
  2. 在这个文件中,我们导入了ReactuseContext挂钩:
import React, { useContext } from 'react'
  1. 接下来,我们从之前创建的src/contexts.js文件导入ThemeContext
import { ThemeContext } from `'./contexts'
  1. 然后,我们定义Header组件和上下文挂钩。我们没有将上下文值存储在theme变量中,而是使用解构直接提取primaryColor值:
const Header = ({ text }) => {
    const { primaryColor } = useContext(ThemeContext)
  1. 最后,我们返回h1元素,就像我们之前在小示例中所做的一样,export返回Header组件:
    return <h1 style={{ color: primaryColor }}>{text}</h1>
}

export default Header

现在我们的Header组件已经定义,我们可以使用它了。

使用 Header 组件

创建Header组件后,我们将在App组件中使用它,如下所示:

  1. 编辑src/App.js,导入Header组件:
import Header from './Header'
  1. 然后,在UserBar组件之前渲染Header组件:
    return (
        <div style={{ padding: 8 }}>
            <Header text="React Hooks Blog" />
            <UserBar user={user} dispatch={dispatch} />

You might want to refactor the React Hooks Blog value into a prop that is passed to the App component (app config), because we are already using it three times in this component.

现在,我们的Header组件将在应用中呈现,我们可以继续在 Post 组件中实现上下文挂钩。

实现 Post 组件的上下文挂钩

接下来,我们想用第二种颜色显示Post标题。为此,我们需要为Post组件定义一个上下文挂钩,如下所示:

  1. 编辑src/post/Post.js,调整import语句导入useContext挂钩:
import React, { useContext } from 'react'
  1. 接下来,我们导入ThemeContext
import { ThemeContext } from '../contexts'
  1. 然后,我们在Post组件中定义一个上下文挂钩,通过解构从主题中获取secondaryColor值:
export default function Post ({ title, content, author }) {
    const { secondaryColor } = useContext(ThemeContext)
  1. 最后,我们使用secondaryColor值来设置h3元素的样式:
    return (
        <div>
            <h3 style={{ color: secondaryColor }}>{title}</h3>

如果我们现在查看我们的应用,我们可以看到这两种颜色都在ThemeContext中正确使用:

Our ThemeContext in action

正如我们所看到的,我们的应用现在使用主标题的主颜色,以及文章标题的次颜色。

定义提供者

现在,当没有定义提供者时,我们的上下文挂钩使用上下文指定的默认值。为了能够更改该值,我们需要定义一个提供者。

让我们开始定义提供者:

  1. 编辑src/App.js,导入ThemeContext
import { ThemeContext } from './contexts'
  1. ThemeContext.Provider组件包装整个应用,提供我们之前设置为默认值的相同主题:
    return (
 <ThemeContext.Provider value={{ primaryColor: 'deepskyblue', secondaryColor: 'coral' }}> <div style={{ padding: 8 }}>
                <Header text="React Hooks Blog" />
                ...
                <PostList posts={posts} />
            </div>
 </ThemeContext.Provider>
    )

我们的应用看起来应该和以前完全一样,但现在我们使用的是提供商提供的价值!

动态更改主题

现在我们已经定义了一个提供者,我们可以使用它来动态地更改主题。我们将使用定义当前主题的状态挂钩,而不是将静态值传递给提供者。然后,我们将实现一个改变主题的组件。

与上下文提供程序一起使用状态挂钩

首先,我们将定义一个新的状态挂钩,我们将使用它来设置上下文提供程序的值。

让我们定义一个状态挂钩,并在上下文提供程序中使用它:

  1. 编辑src/App.js,导入useState挂钩:
import React, { useReducer, useEffect, useState } from 'react'
  1. App组件的开头定义一个新的状态挂钩;在这里,我们将默认值设置为默认主题:
export default function App () {
 const [ theme, setTheme ] = useState({
 primaryColor: 'deepskyblue',
 secondaryColor: 'coral'
 })
  1. 然后,我们将theme值传递给ThemeContext.Provider组件:
    return (
        <ThemeContext.Provider value={theme}>

我们的应用看起来仍然和以前一样,但是我们现在已经准备好动态地改变我们的主题了!

实现 ChangeTheme 组件

主题特性的最后一部分是一个组件,可以通过使用前面定义的状态挂钩来动态更改主题。状态挂钩将重新呈现App组件,这将改变传递给ThemeContext.Provider的值,而ThemeContext.Provider将重新呈现使用ThemeContext上下文挂钩的所有组件。

让我们开始实现ChangeTheme组件:

  1. 创建一个新的src/ChangeTheme.js文件。
  2. 一如既往,我们必须先导入React,然后才能定义组件:
import React from 'react'
  1. 为了以后能够轻松地添加新主题,我们将创建一个常量THEMES数组,而不是手动复制和粘贴不同主题的代码。这将使我们的代码更加简洁,更易于阅读:
const THEMES = [
    { primaryColor: 'deepskyblue', secondaryColor: 'coral' },
    { primaryColor: 'orchid', secondaryColor: 'mediumseagreen' }
]

It is a good idea to give constant values that are hardcoded a special name, such as writing the whole variable name in caps. Later on, it might make sense to put all these configurable hardcoded values in a separate src/config.js file.

  1. 接下来,我们定义一个组件来呈现单个theme
function ThemeItem ({ theme, active, onClick }) {
  1. 在这里,我们渲染一个链接,并通过显示主颜色和次颜色显示主题的小预览:
    return (
        <span onClick={onClick} style={{ cursor: 'pointer', paddingLeft: 8, fontWeight: active ? 'bold' : 'normal' }}>
            <span style={{ color: theme.primaryColor }}>Primary</span> / <span style={{ color: theme.secondaryColor }}>Secondary</span>
        </span>
    )
}

Here, we set the cursor to pointer, in order to make the element appear clickable. We could also use an <a> element; however, this is not recommended if we do not have a valid link target, such as a separate page.

  1. 然后,我们定义ChangeTheme组件,它接受themesetTheme道具:
export default function ChangeTheme ({ theme, setTheme }) {
  1. 接下来,我们定义一个函数来检查主题对象是否是当前活动的主题:
    function isActive (t) {
        return t.primaryColor === theme.primaryColor && t.secondaryColor === theme.secondaryColor
    }
  1. 现在,我们使用.map函数呈现所有可用主题,点击时调用setTheme函数:
    return (
        <div>
            Change theme:
            {THEMES.map((t, i) =>
                <ThemeItem key={'theme-' + i} theme={t} active={isActive(t)} onClick={() => setTheme(t)} />
            )}
        </div>
    )
}
  1. 最后,我们可以导入并呈现ChangeTheme组件,在src/App.js中的Header组件之后:
import ChangeTheme from './ChangeTheme'
// ...
    return (
        <ThemeContext.Provider value={theme}>
            <div style={{ padding: 8 }}>
                <Header text="React Hooks Blog" />
                <ChangeTheme theme={theme} setTheme={setTheme} />
                <br /> 

正如我们所看到的,我们现在有了一种改变应用主题的方法:

Our app after changing the theme, using Context Hooks in combination with a State Hook

现在,我们有了一个通过挂钩消费的上下文,它也可以通过挂钩更改!

示例代码

Chapter05/chapter5_2文件夹中可以找到我们博客应用中主题功能的示例代码。

只需运行npm install即可安装所有依赖项,npm start即可启动应用;然后在浏览器中访问http://localhost:3000(如果它没有自动打开)。

对全局状态使用上下文

在学习了如何使用 React context 在我们的博客应用中实现主题后,我们现在将使用一个上下文来避免手动传递我们的全局应用状态的statedispatch道具。

定义状态上下文

我们首先在src/contexts.js文件中定义上下文。

src/contexts.js中定义StateContext,用于存储state值和dispatch函数:

export const StateContext = React.createContext({
    state: {},
    dispatch: () => {}
})

我们将state值初始化为空对象,dispatch函数初始化为空函数,在未定义提供程序时使用。

定义上下文提供程序

现在,我们将在src/App.js文件中定义上下文提供程序,它将从现有的 Reducer 挂钩中获取值。

现在让我们为全局状态定义上下文提供程序:

  1. src/App.js中,通过调整已有import语句导入StateContext
import { ThemeContext, StateContext } from './contexts'
  1. 然后,通过从App函数返回,我们定义了一个新的上下文提供程序:
    return (
 <StateContext.Provider value={{ state, dispatch }}>
            <ThemeContext.Provider value={theme}>
                ...
            </ThemeContext.Provider>
 </StateContext.Provider>
    )

现在,我们的上下文提供者为应用的其余部分提供了state对象和dispatch函数,我们可以继续使用上下文值。

使用 StateContext

现在我们已经定义了上下文和提供者,我们可以在各种组件中使用state对象和dispatch函数。

我们首先移除在src/App.js中手动传递给组件的道具。删除以下以粗体标记的代码段:

        <div style={{ padding: 8 }}>
            <Header text="React Hooks Blog" />
            <ChangeTheme theme={theme} setTheme={setTheme} />
            <br />
            <UserBar user={user} dispatch={dispatch} />
            <br />
            {user && <CreatePost user={user} posts={posts} dispatch={dispatch} />}
            <br />
            <hr />
            <PostList posts={posts} />
        </div>

当我们使用上下文时,就不再需要手动传递道具了。我们现在可以继续重构组件了。

重构用户组件

首先,我们重构用户组件,然后再转到 post 组件。

现在让我们重构与用户相关的组件:

  1. 编辑src/user/UserBar.js,同时移除那里的道具(应该移除粗体标记的代码),因为我们不再需要手动传递它们:
export default function UserBar ({ user, dispatch }) {
    if (user) {
        return <Logout user={user} dispatch={dispatch} />
    } else {
        return (
            <React.Fragment>
                <Login dispatch={dispatch} />
                <Register dispatch={dispatch} />
            </React.Fragment>
        )
    }
}
  1. 然后,我们导入useContext挂钩和src/user/UserBar.js中的StateContext,以便能够判断用户是否登录:
import React, { useContext } from 'react'
import { StateContext } from '../contexts'
  1. 现在,我们可以使用上下文挂钩从我们的state对象获取user状态:
export default function UserBar () {
 const { state } = useContext(StateContext)
 const { user } = state
  1. 同样,我们在src/user/Login.js中导入useContextStateContext
import React, { useState, useContext } from 'react'
import { StateContext } from '../contexts'
  1. 然后,我们移除dispatch道具,并使用上下文挂钩:
export default function Login () {
 const { dispatch } = useContext(StateContext)
  1. 我们在src/user/Register.js组件中重复相同的过程:
import React, { useState, useContext } from 'react'
import { StateContext } from '../contexts' export default function Register () { const { dispatch } = useContext(StateContext) 
  1. src/user/Logout.js组件中,我们也这样做,但也从state对象获取user状态:
import React, { useContext } from 'react'
import { StateContext } from '../contexts' export default function Logout () { const { state, dispatch } = useContext(StateContext)
    const { user } = state 

我们的用户相关组件现在使用上下文而不是道具。让我们继续重构与 post 相关的组件。

重构 post 组件

现在,剩下要做的就是重构 post 组件;然后,我们的整个应用将使用全局状态的 React 上下文:

  1. 我们从src/post/PostList.js组件开始,在这里我们导入useContextStateContext,移除道具,并使用上下文挂钩:
import React, { useContext } from 'react'
import { StateContext } from '../contexts'

import Post from './Post'

export default function PostList () {
 const { state } = useContext(StateContext)
 const { posts } = state
  1. 我们对CreatePost组件也做了同样的操作,这是我们需要重构的最后一个组件:
import React, { useState, useContext } from 'react'
import { StateContext } from '../contexts'

export default function CreatePost () {
 const { state, dispatch } = useContext(StateContext)
 const { user } = state

我们的应用的工作方式与以前相同,但现在我们使用全局状态的上下文,这使我们的代码更干净,并且避免了传递道具!

示例代码

我们博客应用中全局状态上下文的示例代码可以在Chapter05/chapter5_3文件夹中找到。

只需运行npm install即可安装所有依赖项,npm start即可启动应用;然后在浏览器中访问http://localhost:3000(如果它没有自动打开)。

总结

在本章中,我们首先了解了 React 上下文,它可以替代在多个级别的 React 组件上传递道具。然后,我们了解了上下文提供者和消费者,以及通过挂钩定义消费者的新方法。接下来,我们学习了什么时候使用上下文没有意义,什么时候应该使用控制反转。然后,通过在博客应用中实现主题,我们使用了我们在实践中学到的知识。最后,我们在博客应用中使用了全局状态的 React 上下文。

在下一章中,我们将学习如何使用 React 和 hook 从服务器请求数据。然后,我们将学习React.memo以防止不必要的组件重新渲染,并在需要组件时对延迟加载组件作出 React。

问题

为了回顾我们在本章学到的知识,请尝试回答以下问题:

  1. 上下文可以避免哪些问题?
  2. 上下文由哪两部分组成?
  3. 为了使用上下文,是否需要定义这两个部分?
  4. 使用挂钩而不是传统的上下文消费者的优势是什么?
  5. 什么是上下文的替代方案,我们应该在什么时候使用它?
  6. 我们如何实现动态变化的上下文?
  7. 什么时候为状态使用上下文有意义?

进一步阅读

如果您对我们在本章中探讨的概念的更多信息感兴趣,请阅读以下阅读材料: