资讯专栏INFORMATION COLUMN

react hooks初探

dendoink / 2809人阅读

摘要:可以在不改变组件层级的前提下将带有状态的逻辑抽离出来。因此在中增加了一个特性,允许传入的函数再返回一个函数,这个返回函数的执行时机是下一次触发这个前,以及组件卸载前。当第二个参数为空数组时,返回函数进行清理工作只会在组件卸载时执行。

hooks是什么

hooks是react16.8版本中新增的特性,它让我们能够在不写class的情况下使用状态和其他react特性。也就是说现在我们可以在函数组件中进行状态管理了。

hooks解决了什么问题

组件中带状态的逻辑很难复用

在hooks之前解决这个问题的手段是render props和高阶组件。但是这些方法都需要我们去修改组件层级关系,让代码变得很繁琐。
hooks可以在不改变组件层级的前提下将带有状态的逻辑抽离出来。

复杂组件让人难以理解

原先用class写的组件集成了许多生命周期函数,这些生命周期函数中又包含了许多互不相关的逻辑,比如接口请求,事件绑定等等。这导致组件逻辑复杂之后难以理解也难以测试。
hooks可以将复杂组件的逻辑拆分成更小的函数,这些函数只负责单一的逻辑。拆分后的优点是易懂易测试。

class本身存在的问题

尽管class已经是一个语法糖了,但是react的开发者认为this是一个很麻烦的东西,我们在用class写组件前必须先搞清楚this的工作原理。另外class组件在react编译过程中也存在一些问题,如压缩率并不是很好,热加载不稳定等等。
hooks可以让我们在函数组件中管理状态。尽管完全抛弃class组件还为时尚早,但是有了hooks我们使用class组件的机会将越来越少。

几个常用的hooks useState

基本用法:

import React, { useState } from "react";
export default function Foo() {
    const [selectedKey, setSelectedKey] = useState("");
    const someHandler = () => { setSelectedKey("1") };
    ...
    return ...;
}

上面例子中,useState函数会返回一个数组,数组第一项是我们定义的一个状态selectedKey,第二项是修改这个状态的函数,而userState接收的参数就是这个状态的初始值。当我们使用setSelectedKey修改状态时,react会重新渲染该组件,效果跟setState一样。
这段代码改成class的写法是这样的:

import React, { Component } from "react";
export default class Foo extends Component {
    state = {
        selectedKey: ""
    }
    someHandler = () => {
        this.setState({selectedKey: "1"});
    }
    ...
    render() {
        return ...;
    }
}

区别:

使用class时,我们把组件的所有状态都放在state这个对象中;而一个useState只定义一个状态量,组件有几个状态变量就写几行useState

setState是将新状态merge到老状态中,而useState返回的函数setSelectedKey是将新状态替换老状态,因为一个useState只定义一个状态量所以这边直接替换是没有问题的

useEffect

顾名思义,useEffect就是去做一些有副作用的事。默认情况下useEffect接收一个函数作为参数,在每次render结束后react会去执行这个函数,效果相当于componentDidMount和componentDidUpdate的组合。
下面这段代码表示每次渲染后将状态selectedKey的值设为home:

import React, { useState, useEffect } from "react";
export default function Foo() {
    const [selectedKey, setSelectedKey] = useState("");
    useEffect(()=> {
        setSelectedKey("home");
    });
    ...
    return ...;
}

useEffect的功能还不止这么简单,当我们进行某些副作用操作后,往往需要在组件卸载前做一些清理工作,比如清除定时器,解绑事件监听器等等。因此react在useEffect中增加了一个特性,允许传入的函数再返回一个函数,这个返回函数的执行时机是下一次触发这个useEffect前,以及组件卸载前。
如果我们只想在组件卸载前进行一些清理工作,那就要用到uesEffect的第二个参数了。第二个参数是一个数组,里面可以放这个副作用的依赖,作用是控制这个副作用执行的时机,只有当依赖发生变化的时候才会执行这个副作用。当第二个参数为空数组时,返回函数(进行清理工作)只会在组件卸载时执行。
下面这个例子的作用是在组件首次渲染后以及props.source的值发生变化后执行subscribe( ),在下一次执行subscribe( )前以及组件卸载前执行unsubscribe( ):

useEffect(
  () => {
    const subscription = props.source.subscribe();
    return () => {
      subscription.unsubscribe();
    };
  },
  [props.source]
);

使用useEffect可以模拟react的某些生命周期函数

模拟componentDidMount
useEffect(() => {
    // 这里在mount时执行一次
}, []);
模拟componentWillUnmount
useEffect(() => {
    // 这里在mount时执行一次
    return () => {
    // 这里在unmount时执行一次
    }
}, []);
模拟componentDidUpdate
// useRef会返回一个对象,这个对象有个current属性,值为传给useRef的参数
// useRef在组件生命周期中只初始化一次,之后它会帮我们保存返回的对象
const mounting = useRef(true);
useEffect(() => {
    if (mounting.current) {
    mounting.current = false; // 对current的修改会被useRef保存,但修改不会引起重新渲染
    } else {
    // 这里只在update时执行
    }
});
举例

需求:使用antd的menu组件实现一个侧边栏,类似下面的样子,当用户输入不同url时侧边栏需要联动

实现:

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

export default function Sider() {
    const [selectedKey, setSelectedKey] = useState("");
    const [openKeys, setOpenKeys] = useState([]);
    // 组件更新时根据url更新选中的菜单项
    useEffect(() => {
        const key = getSelectedKey(); // 根据url得到选中的菜单项
        setSelectedKey(key);
    });
    // 组件mount时根据url自动展开子菜单
    useEffect(() => {
        const key = getOpenKey(); // 根据url得到应该展开的菜单
        setOpenKeys([key]);
    }, []);
    ...
    return (
         setOpenKeys(openKeys)}
        >
        ...
        
    );
}

分析:

组件使用useState定义了两个状态量:selectedKey和openKeys;

第一个useEffect用于更新selectedKey,它会在每次render后从url中获取当前选中的菜单项,然后更新selectedKey;

第二个useEffect用于首次进入网站时,从url中获取应该展开的菜单并更新openKeys,它只在组件创建时执行,相当于componentDidMount;

当用户点击父菜单想要展开或收起时,通过onOpenChange事件来触发openKeys的更新;

当用户点击子菜单想要选中时,会先触发路由跳转(这个逻辑无法从代码中获取,请自行脑补),路由改变会引发改组件重新渲染,继而触发第一个useEffect来更新selectedKey。

自定义hooks

文章开头已经讲到了hooks可以很方便的实现带状态的逻辑复用。
下面是一个简单的自定义hooks,功能是请求接口并返回数据:

import { useState, useEffect } from "react";

export default function useUserInfo() {
    const [userInfo, setUserInfo] = useState(null);
    useEffect(() => {
        fetch("https://react.hooks.com/api/userinfo").then(
            data => {
                setUserInfo(data);
            },
        );
    }, []);
    return userInfo;
}

使用也很简单,当Home组件加载时会通过useUserInfo这个自定义hooks去请求接口,接口数据返回时Home组件会自动更新:

import React from "react";
import useUserInfo from "../../hooks/useUserInfo";

export default function Home() {
    const userInfo = useUserInfo();
    
    return 
{userInfo}
; }
hooks + context进行全局状态管理

react提供了useContext这个hooks使得在函数组件中使用context变得更加方便。
如果项目没有复杂到需要上redux,可以使用下面的方法进行全局状态管理。
首先创建一个context:

// globalContext.js
import React from "react";

export default React.createContext({
    musicianPlan: "1",
    language: "zh",
    changeMusicianPlan: () => {},
    changeLanguage: () => {},
});

然后定义一个高阶组件,用于管理context中的状态:

// globalState.jsx
import React, { useState } from "react";
import GlobalContext from "./globalContext";

export default function GlobalState(props) {
    const [musicianPlan, setMusicianPlan] = useState("1");
    const [language, setLanguage] = useState("zh");
    const changeMusicianPlan = planId => {
        setMusicianPlan(planId);
    };
    const changeLanguage = lang => {
        setLanguage(lang);
    };
    return (
        
            {props.children}
        
    );
}

将这个高阶组件放到组件树的顶层:

// app.jsx
import React from "react";
import GlobalState from "./context/globalState";

export default function App() {
    return (
        
            
                ...
            
        
    );
}

在Header组件中用useContext这个hooks获取到context,然后调用changeMusicianPlan方法来改变全局状态musicianPlan:

import React, { useContext, ReactElement } from "react";
import { Select } from "antd";
import GlobalContext from "../../context/globalContext";

const Option = Select.Option;

export default function Header() {
    const globalContext = useContext(GlobalContext);
    return (
        
    );
}

在Home组件中同样使用useContext获取context,然后使用全局状态musicianPlan进行动态渲染:

import React, { useContext } from "react";
import GlobalContext from "../../context/globalContext";

export default function Home() {
    const globalContext = useContext(GlobalContext);
    return 
{globalContext.musicianPlan}
; }

当然上面的方法也可以用于某个局部组件树的状态管理,将状态进行拆分管理不仅提高运行效率也更清晰易懂。

使用hooks的注意事项

hooks只能在组件内部的最顶层调用,不能将其放在循环语句、条件语句或者子函数内;

hooks只能在函数组件或自定义hooks中使用;

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

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

相关文章

  • 解读React源码(一):初探React源码

    摘要:前言的基本概念组件的构建方法以及高级用法这背后的一切如何运转深入内部的实现机制和原理初探源码代码组织结构包含一系列的工具方法插件包含一系列同构方法包含一些公用或常用方法如等包含一些测试方法等包含一些边界错误的测试用例是代码的核心部分它包含了 前言 React的基本概念,API,组件的构建方法以及高级用法,这背后的一切如何运转,深入Virtual DOM内部的实现机制和原理. 初探Rea...

    Eminjannn 评论0 收藏0
  • React 初探

    摘要:各个组件维护自己的状态和,当状态变更,自动重新渲染整个组件。形式的定义的组件是以的形式来创建的组件的,是目前极为推荐的创建有状态组件的方式,最终会取代形式相对于可以更好实现代码复用。组件名称首字母必须大写。变量名用包裹,且不能加双引号。 目前在前端开发领域,框架Angular、react和vue占据着主流的地位而且可能会持续比较长的一段时间。三门框架中,从数据绑定机制来看,vue和an...

    levy9527 评论0 收藏0
  • React 初探

    摘要:各个组件维护自己的状态和,当状态变更,自动重新渲染整个组件。形式的定义的组件是以的形式来创建的组件的,是目前极为推荐的创建有状态组件的方式,最终会取代形式相对于可以更好实现代码复用。组件名称首字母必须大写。变量名用包裹,且不能加双引号。 目前在前端开发领域,框架Angular、react和vue占据着主流的地位而且可能会持续比较长的一段时间。三门框架中,从数据绑定机制来看,vue和an...

    trilever 评论0 收藏0
  • react-redux初探理解

    摘要:它的作用就是像它的名字那样,建立一个从外部的对象到组件的对象的映射关系。比如表示从整个的表示当前组件容器的用来建立组件的参数到方法的映射比如表示它定义了哪些用户的操作应该当作,传给。 最近做的项目加入了react-redux,对react-redux一直没理解透彻,最近有时间把react-redux梳理了一番,希望能够帮助到大家, 首先有这几个文件,action,reducer,sag...

    ziwenxie 评论0 收藏0
  • 初探react技术栈(一)

    摘要:相信用的同学也不少找到函数在其中中添加启用编译。。。react 最近已经开始使用react技术栈了,从头开始搭建项目,有必要的记录一下配置的过程以及项目分层的思路,这次后台项目采用的主要采用react-create-app脚手架以及Ant DesignUI 以及多语言react-intl create-react-app 这是官方维护的脚手架应用 我们一般也采用这个 $ npm or c...

    刘玉平 评论0 收藏0

发表评论

0条评论

dendoink

|高级讲师

TA的文章

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