资讯专栏INFORMATION COLUMN

Hooks + Context:状态管理的新选择

tommego / 3193人阅读

摘要:用户点击改变全局状态崔然渲染整颗组件树有没有解决方案呢当然有创建一个只接收的新组件,并将组件中的逻辑都移到组件中。最终的示例使用全局状态和生成全局状态和崔然完整示例见结论在和出现之前,缺乏自带的全局状态管理能力。

React 16.3 版本,正式推了出官方推荐的 context API —— 一种跨层级的数据传递方法。React 16.8 版本,推出了全新的 hooks 功能,将原本只有 class 组件才有的状态管理功能和生命周期函数功能,赋予了 function 组件。Hooks 配合 context 一起使用,为 react 状态管理提供了一种新的选择。这可能会减少开发者对 redux 等状态管理库的依赖。

本文首先会对官方的 context 作简单介绍,并搭建一个十分简单的使用全局状态的应用。然后再对 hooks 的基本 API useState useEffect 做基本介绍。接着使用 useContext hooks 对应用进行重构,让 context 的使用变得更优雅。再使用 useReducer hooks 来管理多个状态。最后,待充分理解 hooks 和 context 之后,我们将它们搭配起来用,对整个应用进行状态管理。

Context 概述

React 中存在一个众所周知的难题,那就是如何管理全局状态。即便是最基础的全局状态跨越层级传递,也是非常麻烦。此时,首选的解决方案就是使用状态管理库,如 redux。Redux 本身是一个 API 非常少的状态管理工具,其底层也是用 context 实现的。在一些状态管理不是那么复杂,但是又有跨越层级传递数据的需求时,不妨考虑使用 context 直接实现。

例如,一个 Page 组件包含全局状态 user ,需要经过多次props的传递,层级很深的 Avatar 组件才能使用它。


// ... render ...

// ... render ...

// ... render ...
Context :跨层级传递数据

Context 提供了一种方法,解决了全局数据传递的问题,使得组件之间不用显式地通过 props 传递数据。

React.createContext: 创建一个 Context 对象,该对象拥有 Provider 和 Consumer 属性。

Context.Provider: 接受一个 value 参数,在 value 参数更新的时候通知 Consumer。

Context.Consumer: 订阅 value 参数的改变。一旦 value 参数改变,就会触发它的回调函数。

使用 context 重构的之后,跨层级传递数据就变得容易很多:

// 创建一个 context
const UserContext = React.createContext();

class App extends React.Component {
 state = { user: "崔然" };

 setUser = user => {
   this.setState({ user });
};

 render() {
   // 设置 context 当前值为 {user, setUser}
   return (
     
       
     
  );
}
}

// ... Page render ...

// ... PageLayout render ...

// ... NavigationBar render ...
// 无论组件有多深,都可以**直接**读取 user 值

{ ({user, setUser}) =>  }
避免全局渲染

但是,在使用 context 时,有些写代码的小技巧,需要特别注意。不然在全局状态改变时, Provider 的所有后代组件都会重新渲染。例如,用户点击 Avatar 组件后,将 崔然 更新为 CuiRan,这时会调用根组件的 setUser 方法。根组件 setState({ user }) 更新状态,会导致整颗组件树重新渲染。

const Avatar = ({ user, setUser }) => {
 // 用户点击改变全局状态
 return 
setUser("CuiRan")}>{user}
; }; class App extends React.Component { state = { user: "崔然" }; setUser = user => { this.setState({ user }); }; // ... 渲染整颗组件树 }

有没有解决方案呢?当然有!

创建一个只接收 props.children的新组件 AppProvider ,并将 App 组件中的逻辑都移到 AppProvider组件中。通过备注的 console 日志可以看到,该方式避免了不必要的渲染。

const Avatar = ({ user, setUser }) => {
 // 用户点击改变全局状态
 return 
setUser("CuiRan")}>{user}
; }; // 将 App 逻辑移到 AppProvider const UserContext = React.createContext(); class AppProvider extends React.Component { state = { user: "崔然" }; setUser = user => { this.setState({ user }); }; render() { return ( {this.props.children} ); } } // APP 只保留根组件最基本的 JSX 嵌套 const App = () => ( ); // ... Page not render ... // ... PageLayout not render ... // ... NavigationBar not render ... // Consumer 监听到 Provider value 的改变 {/* **only** Avatar render */ } {({user, setUser}) => }

为什么?为什么把 App 上的全局状态及设置状态的方法移到 AppProvider 上,就能避免不必要的渲染?在 props.children 方案中:

// 1. App 本身没有全局状态改变,因此  不会重渲染
const App = () => (
 
   
 
);

// 2. Provider value 变化,因此会触发 Consumer 的监听函数。

{  /* 3. this.props.children 只是  的引用
  但并不会调用 ,即调用 createElement("Page") */ }
{this.props.children}

虽然,context 解决了数据跨层级传输的问题,但是还遗留了一些问题:

Consumer 的回调取值的写法 { value => <> 不优雅。

单个状态和状态改变很好传递,但是多个状态和对应的状态改变传递依旧不方便。

多个全局状态,如何管理?

没关系,且看 hooks 闪亮登场,将这些问题一一击破。

Hooks 概述

考虑到有些朋友不是很了解 hooks,本文先介绍一下 hooks 的基本用法 。Hooks 让我们可以在 function 组件中使用状态和生命周期函数,并赋予了一些更强大的功能。这也意味着,在 React 16.8 之后,我们再不需要写 class 组件。再强调一次,我们再不需要写 class 组件!

useState: 允许在 function 组件中,声明和改变状态。在此之前,只有 class 组件可以。

useEffect:允许在 function 组件中,抽象地使用 React 的生命周期函数。开发者可以使用更函数式的、更清晰的 hooks 的方式。

使用 hooks 对带有本地状态的 Avatar 组件进行重构说明:

import React, { useState, useEffect } from "react";

const Avatar = ({ user, setUser }) => {
 // 创建 user 状态和修改状态的函数
 const [user, setUser] = useState("崔然");
 
 // 默认 componentDidMount/componentDidUpdate 时会触发回调
 // 也可以使用第二个参数,指定触发时机
 useEffect(() => {
   document.title = `当前用户:${user}`;
});
 
 // 使用 setUser 改变状态
 return 
setUser("CuiRan")}>{user}
; };

接着,我们继续了解 context 的 hooks 用法 —— userContext

useContext:更优雅的 context

在 react 引入 hooks 后,使得 context 的消费更简单了,开发者可以很优雅地直接获取。下面我们使用 useContextUser 组件进行重构。

// 重构前
const User = () => {
 return (
   
    {({ user, setUser }) => }
   
);
};

// 重构后
const User = () => {
 // 直接获取,不用回调
 const { user, setUser } = useContext(UserContext);
 return ;
};

就是这么简单!无论 context 包含什么,是数字、字符串,还是对象、函数,都可以通过useContext访问它。

useReducer:自带的状态管理

当组件同时使用多个useState方法时,需要一个一个的声明。状态多了,就一大溜的声明。比如:

const Avatar = ({ user, setUser }) => {
 const [user, setUser] = useState("崔然");
 const [age, setAge] = useState("18");
 const [gender, setGender] = useState("女");
 const [city, setCity] = useState("北京");
 // more ...
};

useReducer 实际是 useState 的一个变种,解决了上述多个状态,需要多次使用 useState 的问题。

当你看到 useReducer 时,是不是非常熟悉?想起了redux 中的 reducer 函数。对!React 提供的 useReducer 函数,它就是使用 (use) reducer 函数作为参数。useReducer 接受的 reducer 参数,本质和 redux 的是一样的。然后 useReducer 会返回 statedispath 方法,返回的 dispath ,本质上和 redux 的也是一样的。

让我们使用 useReducer 将带有本地状态的 Avatar 组件重构一下:

const reducer = (state, action) => {
 switch (action.type) {
   case "CHANGE_USER":
     return { ...state, user: action.user };
   case "CHANGE_AGE":
     return { ...state, age: action.age };
   // more ...  
   default:
     return state;
}};

const Avatar = ({ user, setUser }) => {
 const [state, dispatch] = useReducer(
   reducer,
  { user: "崔然", age: 18 }
);

 return (
   <>
     
dispatch({ type: "CHANGE_USER", user: "CuiRan" })}> {state.user}
dispatch({ type: "CHANGE_AGE", age: 17 })}> {state.age}
)};

更进一步地,将 useReducer和直接对比 redux 试试,你会发现它们之间惊人的相似:

// react hooks
const [state, dispatch] = useReducer(reducer, [initialArg]);

// redux
const store = createStore(reducer, [initialArg])
const state = store.getState()
const dispatch = store.dispatch

还记得我们再 context 中介绍的 provider 和 consumer 吗?再联想一下,它们的作用不就是和 react-redux 中的 provider 和 connect 一模一样 —— 将数据跨层级的进行传递!

// react hooks

 

// ... 跨层级传递 ...
const { state, dispacth } = useContext(GolbleContext);

// react-redux

 

// ... 跨层级传递 ...
connect(mapStateToProps, actionCreators)(ConsumerComponent)

到现在为止,react 可谓是自带了大半个 redux 的 API 了。那么我们不就可以把 redux 的状态管理思路直接搬过来即可。

最后,只需要将全局状态放到在 App 组件的顶层。最终的示例:

const Avatar = ({{ state, dispatch }) => {// ...})
// 使用全局状态和 dispatch
const User = () => {
 const { state, dispatch } = useContext(UserContext);
 return ;
};

// 生成全局状态和 dispatch
const reducer = (state, action) => {// ...};
const AppProvider = ({ children }) => {
 const [state, dispatch] = useReducer(reducer, { user: "崔然", age: 18 });

 return (
   
    {children}
   
)};

完整示例见:https://github.com/jiangleo/h...

结论

在 hooks 和 context 出现之前,react 缺乏自带的全局状态管理能力。即便很小的应用,一旦要用到全局状态,要么使用 props 多层级的进行传输,要么就只能引入 redux 等第三方状态管理工具。

在 hooks 和 context 出现之后,react 自身提供了一种简单的全局状态管理的能力。如果你的项目比较简单,只有少部分状态需要提升到全局,大部分组件依旧通过本地状态来进行管理。这时,使用 hooks + context 进行状态管理的是强烈推荐的。打苍蝇,用不着大炮。

此外,我们也观察到,社区中一些新型的基于 hooks + context 的状态管理库正在快速崛起,比如 easy-peasy、constate。另一方面,成熟的 redux 也在 7.x 版本,开始引入 hooks API 开始升级。我们也会持续保持关注,探索 hooks 时代状态管理的最佳实践。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/105348.html

相关文章

  • React组件设计实践总结05 - 状态管理

    摘要:要求通过要求数据变更函数使用装饰或放在函数中,目的就是让状态的变更根据可预测性单向数据流。同一份数据需要响应到多个视图,且被多个视图进行变更需要维护全局状态,并在他们变动时响应到视图数据流变得复杂,组件本身已经无法驾驭。今天是 520,这是本系列最后一篇文章,主要涵盖 React 状态管理的相关方案。 前几篇文章在掘金首发基本石沉大海, 没什么阅读量. 可能是文章篇幅太长了?掘金值太低了? ...

    ideaa 评论0 收藏0
  • 使用react hooks实现自己的context-redux

    摘要:首发自我的博客,欢迎注如要运行本文的代码,请先确认自己的版本已支持出来已经有段时间了,本文不对的具体用法作介绍,而是使用实现一个简易的基于的使用实现初版自带了供我们使用,它接受两个参数,一是函数,二是初始,并返回和函数,如下这个函数自己实现 首发自我的github博客,欢迎star 注:如要运行本文的代码,请先确认自己的react版本已支持hooks react hooks出来已经有段...

    Jackwoo 评论0 收藏0
  • react hooks初探

    摘要:可以在不改变组件层级的前提下将带有状态的逻辑抽离出来。因此在中增加了一个特性,允许传入的函数再返回一个函数,这个返回函数的执行时机是下一次触发这个前,以及组件卸载前。当第二个参数为空数组时,返回函数进行清理工作只会在组件卸载时执行。 hooks是什么 hooks是react16.8版本中新增的特性,它让我们能够在不写class的情况下使用状态和其他react特性。也就是说现在我们可以在...

    dendoink 评论0 收藏0
  • 30分钟精通React今年最劲爆的新特性——React Hooks

    摘要:所以我们做的事情其实就是,声明了一个状态变量,把它的初始值设为,同时提供了一个可以更改的函数。 你还在为该使用无状态组件(Function)还是有状态组件(Class)而烦恼吗? ——拥有了hooks,你再也不需要写Class了,你的所有组件都将是Function。 你还在为搞不清使用哪个生命周期钩子函数而日夜难眠吗? ——拥有了Hooks,生命周期钩子函数可以先丢一边了。 你在还...

    icattlecoder 评论0 收藏0
  • React Hooks实现异步请求实例—useReducer、useContext和useEffec

    摘要:本文是学习了年新鲜出炉的提案之后,针对异步请求数据写的一个案例。注意,本文假设了你已经初步了解的含义了,如果不了解还请移步官方文档。但不要忘记和上下文对象可以看做是写法的以及三个钩子函数的组合。 本文是学习了2018年新鲜出炉的React Hooks提案之后,针对异步请求数据写的一个案例。注意,本文假设了:1.你已经初步了解hooks的含义了,如果不了解还请移步官方文档。(其实有过翻译...

    Code4App 评论0 收藏0

发表评论

0条评论

tommego

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<