首页与我联系

「React Hooks 学习笔记」关于 useContext 的使用介绍(六)

By 前端达人
Published in 4-React
October 11, 2022
2 min read
「React Hooks 学习笔记」关于 useContext 的使用介绍(六)

推荐阅读

  1. State Hook 的使用介绍(一)

  2. useEffect Hook 的使用介绍(二)

  3. React.memo 的使用介绍(三)

  4. useMemo 和 useCallback 的使用介绍(四)

  5. useRef 的使用介绍(五)

    1_R2VHgosi7SeFyWGLBdDbFw.jpg

本文内容

本文内容共 2000 字,预计阅读时长 5 分钟

  1. 前言
  2. 什么是 Context Object?
  3. 如何使用 Provider?
  4. 什么是 useContext?
  5. 关于 useContext 的应用
  6. useContext 使用总结

前言

一般来说,组件之间的值传递是通过 pro 属性一层层的传给子组件的,如下图所示:

1_TnVV1bnhmNqHbHasY6kiXw.jpg

从上图可以看出,及时中间的几个组件及时没有用到相关的属性,为了传递给最后一个 button 组件,每个组件都需要带上这个属性,如下段代码所示:

import React, { useState, useContext } from 'react';
import { data } from '../../../data';

const ContextAPI = () => {
  const [people, setPeople] = useState(data);
  const removePerson = (id) => {
    setPeople((people) => {
      return people.filter((person) => person.id !== id);
    });
  };
  return (
    <>
      <h3>prop drilling</h3>
      <List people={people} removePerson={removePerson} />
    </>
  );
};

const List = ({ people, removePerson }) => {
  return (
    <>
      {people.map((person) => {
        return (
          <SinglePerson
            key={person.id}
            {...person}
            removePerson={removePerson}
          />
        );
      })}
    </>
  );
};

const SinglePerson = ({ id, name, removePerson }) => {
  return (
    <div className='item'>
      <h4>{name}</h4>
      <button onClick={() => removePerson(id)}>remove</button>
    </div>
  );
};

export default ContextAPI;

如上述代码所示,你也许发现了 removePerson 这个事件属性,通过 pro 属性一层层的传递给 SinglePerson 组件,如果组件多的话,我们就不知道在哪个组件会用到这个事件,会不会有更好的方式呢?

那就是 Context

介绍 useContext 前一定要先來说 Context 因为 useContext 只是他之中Context.Consumer 的 语法糖而已。

什么是 Context Object ?

Context Object 通过 createContext() 进行创建,对应有两个属性:

  • Provider:传递 value 这个值
  • Consumer: 接收 value 这个值
const testContext = createContext();
const { Provider, Consumer } = testContext;

如何使用 Provider?

我们会用 Context.Provider 包含 value 属性进行传递值

1_JerKVgFZAvi6ha_qAT1-JA.jpg

我们使用 Context.Provider 包含子组件,并传递 value 属性,这样就能把值传递给其中的自组件,示例代码如下:

const PersonContext = React.createContext();
// two components - Provider, Consumer

const ContextAPI = () => {
  const [people, setPeople] = useState(data);
  const removePerson = (id) => {
    setPeople((people) => {
      return people.filter((person) => person.id !== id);
    });
  };
  return (
    <PersonContext.Provider value={{ removePerson, people }}>
      <h3>Context API / useContext</h3>
      <List />
    </PersonContext.Provider>
  );
};

const List = () => {
  const mainData = useContext(PersonContext);
  console.log(mainData);
  return (
    <>
      {mainData.people.map((person) => {
        return <SinglePerson key={person.id} {...person} />;
      })}
    </>
  );
};

const SinglePerson = ({ id, name }) => {
  const { removePerson } = useContext(PersonContext);

  return (
    <div className='item'>
      <h4>{name}</h4>
      <button onClick={() => removePerson(id)}>remove</button>
    </div>
  );
};

从上述代码,大家可能看出我们并没有在 ListSinglePerson 传递 peopleremovePerson 属性,而是通过 useContext 获取 Provider 的 value 的值。

什么是 useContext ?

从上个示例我们看出,我们使用useContext 获取 Provider 的 value 的值。

接收一个 context object(React.createContext 的回传值)并回传该 context 目前的值。 Context 目前的值是取决于由上层 component 距离最近的 MyContext.Provider 的 value prop。

除了使用 useContext 获取值,我们还可以通过 Context.Consumer 获取值,如下段代码所示:

app.js 文件

// App.js
export const ThemeContext = React.createContext() 

const App = () => {
    const [dark, setDark] = useState(true);
    
    return (
        <>
            <ThemeContext.Provider value={dark}>
              <FunctionComponent />
              <ClassComponent />
            </ThemeContext.Provider>
        </>
    );
}

// FunctionComponent.js
const FunctionComponent = () => {
    return (
        <ButtonGroupComponent />
    );
}

ButtonGroupComponent.js

// ButtonGroupComponent.js
import { ThemeContext } from './App.js';

const ButtonGroupComponent = () => {
   const themeStyle = (dark) => {
      return {
        backgroundColor: dark ? '#2c3e50': '#f1c40f',
        color: dark ? '#ecf0f1' : '#2c3e50'
      }
    };
  return (
    <ThemeContext.Consumer>
      { dark =>
        <button style={themeStyle(dark)}>Class Component</button>
      }
   </ThemeContext.Consumer>
  );
}

另外有一点需要说明的,Context 也可以包含 Context,就是互相嵌套,如下段代码所示:

// ThemeProvider.js
const ThemeContext = React.createContext();
const ThemeUpdate = React.createContext();

const ThemeProvider = ({children}) => {
  const [dark, setDark] = useState(true);
  
  const changeTheme = () => {
    setDark(prevDark => !prevDark)
  }
  
  return (
    <ThemeContext.Provider value={dark}>
      <ThemeUpdate.Provider value={changeTheme}>
        {children}
      </ThemeUpdate.Provider>
    </ThemeContext.Provider>
   )
}

// App.js
const App = () => {
  return (
    <ThemeProvider>
      <FunctionComponent />
      <ClassComponent />
    </ThemeProvider>
  )
};

关于 useContext 的应用

学习完了 useContext, 我们来通过几个实例来深入理解下 useContext。

1、用户登录欢迎页

用户登录成功后的欢迎页,是一个很常见的需求,如下图所示,登录成功后显示用户的头像

000025.webp

接下来我们使用 useContext 完成此需求,新建 UserContext.js 文件,如下图所示:

const UserContext = React.createContext()
export default UserContext

在应用中,我们可以通过 UserContext.Provider 初始化用户数据,示例代码如下:

import UserContext from './UserContext'
const App = ({ initialUser }) => {
  const [user, changeUser] = useState(initialUser)
  const value = { user, changeUser }
  return (
    <UserContext.Provider value={value}>
      <div>
        <Header />
        <Site />
      </div>
    </UserContext.Provider>
  )
}

App 组件创建一个初始化用户状态 initialUser。并将值传递给 user 数据状态,并通过 changeUser 更改其值,以通过 UserContext.Provider 将值进行共享。

在 Logo 组件里,我们可以通过 useContext 进行获取值,示例代码如下:

import UserContext from './UserContext'
const Logo = () => {
  const { user } = useContext(UserContext)
  return (
    <div>
      <img url={ user.imageUrl } />
      <span>{ user.username }</span>
    </div>
  )  
}

同样在欢迎语组件里,我们也做类似的调用,示例代码如下:

import UserContext from './UserContext'
const Greeting = () => {
  const { user } = useContext(UserContext)
  return <h1>Hello {user.username}</h1>
}

更改 context 的值

接下来我们增加一个退出按钮,更改 UserContext 中 user 的值,并根据值动态显示用户的信息或登录提示信息,修改后的 Logo 组件代码如下:

import UserContext from './UserContext'
const Logo = () => {
  const { user, changeUser } = useContext(UserContext)
  const { imageUrl, username } = user
  const authenticated = username != undefined
  const onLogout = () => { changeUser({}) }
  const onLogin = () => { // redirect to LoginForm }
  return (
    <div>
      {authenticated ? (
        <>
          <img url={ imageUrl } />
          <span>{ username }</span>
           <button onClick={onLogout}>Logout</button>
        </>
      ) : (
           <button onClick={onLogin}>Login</button>
      )}
    </div>
  )  
}

在上述代码中,changeUser 函数是通过 UserContext 提供的。它根据用户信息是否有值,显示用户信息、“注销” 或 “登录”按钮。

如果用户未通过身份验证,则会显示“登录”按钮。点击后,会触发 onLogin 事件处理程序,我们可以将用户重定向到 LoginForm(此处省略重定向代码):

const LoginForm = () => {
  const { changeUser } = useContext(UserContext)
  const onSubmit = (_user) => {
    changeUser(_user)
    // redirect to home page
  }
  return ...
}

LoginForm 还使用 UserContext 并读取 changeUser 属性。成功提交表单后,我们可以再次使用 changeUser 将空用户更改为经过身份验证的用户。同样,新更新的值会传播给所有消费者,包括LogoGreeting 组件

用户登录和注销通常是站点的常见需求,因为它直接影响站点的身份验证和授权。特别适合局部刷新界面,context 非常适合读取和写入用户对象,而无需担心各种组件之间的数据不同步。

2、样式模式切换

涉及 context 的一个常见的用法是主题化,它允许用户根据自己的喜好在浅色和深色主题之间切换。

000033.webp

要实现此功能,让我们先从 ThemeContext 开始:

const ThemeContext = React.createContext({
  mode: 'light',  // or 'dark'
})

我们可以使用 mode 对象设置默认主题,该属性是一个字符串,用于保存“light”或“dark”。

任何需要主题的组件都可以从 ThemeContext 中读取设置。让我们构建一个主题按钮:

const Button = () => {
  const theme = useContext(ThemeContext)
  return (
    <ButtonStyle mode={theme.mode}>
     Ok
    </ButtonStyle>
}

我们可以根据 ButtonStyle 组件的属性,为按钮的样式设置主题:

const color = props =>
  props.mode === 'light' : 'black' ? 'white'
const ButtonStyle = styled.button`
  color: ${color};
`

我们可以将需要用到样式切换的组件放置在 ThemeContext.Provider 内部,这样就能做到相关组件的样式的切换。

const App = ({ theme }) => {
  return (
    <ThemeContext.Provider value={theme}>
      ...
    </ThemeContext.Provider>
}

除了可以切换浅色和设色主题,还可以切换站点的样式主题,比如蓝色主题,你可以自定义更多的主题样式,示例代码如下:

const ThemeContext = React.createContext({
  primaryColor: 'blue',
  secondaryColor: 'red'
})

接下来我们,我们可以将 ThemeContext 的值通过属性的方式传递给子组件,示例代码如下:

const color = props => props.primaryColor
const ButtonStyle = styled.button`
  color: ${color};
`

3、表格(Table context

表格的需求也很常见,这次我们使用 context 的方式创建一个表格,如下图所示,同时可以删除表格对应的行数据。

000040.webp

首先我们先定义单元格组件,就是表格每行对应的单元格,默认的 DefaultCell,示例代码如下:

const DefaultCell = ({ value }) => {
  return <div>{value}</div>
}

接下来我们来定义表格的水果数据,数据形式如下所示:

const fruits = [
  { title: 'Apple', status: true },
  { title: 'Orange', },
  { title: 'Strawberry' },
  { title: 'Pineapple', status: true },
  { title: 'Watermelon' }
]

接下来我们定义显示水果名称的单元格,同时依据水果的状态,显示 Nice 或 Ok,StatusCell 单元格的代码如下:

const StatusCell = ({ value }) => {
  const s = value ? 'Nice' : 'Ok'
  return <div>{s}</div>
}

我们继续需要定一个单元格组件 TableCell ,根据单元格的属性不同,我们来显示不同的单元格组件,比如显示水果名称的单元格或操作单元格的按钮。

const TableCell = ({ col }) => {
  const Component = col.Cell || DefaultCell
  return <Component />
}

接下来我们来定义列数据(表头)的形式,示例代码如下:

const cols = [
  { name: 'title' },
  { name: 'status', Cell: StatusCell }
]

接下来我们继续设计本案例的核心部分,设计 TableContext 数据部分,如下段代码声明 TableContext ,示例代码如下:

const TableContext = React.createContext({
  rows: [],
  cols: [],
  row: {}
})
export default TableContext

接下来我们定义 TableRow 组件,读取共享的TableContext 数据,用来加载每行数据,示例代码如下:

import TableContext from './TableContext'
const TableRow = ({ row }) => {
  const table = useContext(TableContext)
  const value = { ...table, row }
  const cols = table.cols
  return (
    <TableContext.Provider value={value}>
      <TableRowStyle>
        {cols.map(col => {
          <TableCell
            row={row}
            col={col}
          />
        })}
      </TableRowStyle>
    </TableContext.Provider>
  )
}

接下来我们调整单元格的数据源形式,方便我们更加灵活的显示自定义单元格,cols 数据源如下,根据不同 name 显示不同的单元格组件:

const cols = [
  { name: 'title' },
  { name: 'status', Cell: StatusCell },
  { name: 'combo', Cell: ComboCell }
]

这里新增了 ComboCell 组件,我们需要进行定义,示例代码如下:

const ComboCell = () => {
  const { row } = useContext(TableContext)
  const s = row.status ? 'Nice' : 'Ok'
  return <div>{row.title} – {s}</div>
}

接下来我们将 TableRow 嵌入 Table 组件里,根据数据源渲染表格的行,代码如下:

const Table = ({ cols, rows, setRows }) => {
  const value = { cols, rows, setRows }
  return (
    <TableContext.Provider value={value}>
      <TableStyle>
        {rows.map(row => {
          <TableRow row={row} />
        })}
      </TableStyle>
    </TableContext.Provider>
  )
}

根据需求,我们还需在最后一列,自定义删除操作,示例代码如下:

const DeleteCell = () => {
  const { row, rows, setRows } = useContext(TableContext)
  const onClick = () => {
    const newRows = rows.filter(r => r.title !== row.title)
    setRows(newRows)
  }
  
  return (
    <div>
      <button onClick={onClick}>Remove</button>
    </div>
  )
}

最后我们来定义 APP 组件,将相关数据传递 Table 组件,示例代码如下:

const cols = [
  { name: 'combo', Cell: ComboCell },
  { name: 'action', Cell: DeleteCell }
]
const App = () => {
  const [rows, setRows] = useState(fruits)
  return <Table
    rows={rows}
    cols={cols}
    setRows={setRows}
  />
}

到这里本示例就介绍完了,完整版的源码和示例预览,大家可以到以下链接地址进行体验:

https://codepen.io/windmaomao/pen/VwzWeMa

useContext 使用总结

到这里关于 useContext 的使用总结就到这里介绍完了,我们学习了什么是 useContext,以及如何使用 useContext 。通过此特性,我们可以在组件之间共享值,解决了 props 属性在组件之间层层传递的问题。但是并不是说一定要使用 useContext 去替代原本的 props 方法,而是根据实际的情况选择是否合适,毕竟现在 context 有一个很大的问题,每当数据状态改变,与其相关的所有组件都会更新。因此不会常变的值才适合使用 useContext,比如主题样式。

前端达人公众号.jpg

注:本文属于原创文章,版权属于「前端达人」公众号及 qianduandaren.com 所有,未经授权,谢绝一切形式的转载


Tags

reacthook
Previous Article
「手写原生项目专题」一个带计时进度的在线答题应用(五)
前端达人

前端达人

专注前端知识分享

Table Of Contents

1
推荐阅读
2
本文内容
3
前言
4
什么是 Context Object ?
5
如何使用 Provider?
6
什么是 useContext ?
7
关于 useContext 的应用
8
useContext 使用总结

相关文章

「React Hooks 学习笔记」关于Custom Hooks 的使用介绍(八)
October 28, 2022
1 min

前端站点

VUE官网React官网TypeScript官网

公众号:前端达人

前端达人公众号