热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Reacthooks简介

什么是Hooks?不通过编写类组件的情况下,可以在组件内部使用状态(state)和其他React特性(生命周期,context)的技术Ho

什么是 Hooks?

不通过编写类组件的情况下,可以在组件内部使用状态(state) 和其他 React 特性(生命周期,context)的技术

Hooks 为什么会出现

在之前的 React 版本中,组件分为两种:函数式组件(或无状态组件(StatelessFunctionComponent))和类组件,而函数式组件是一个比较的纯洁的 props => UI 的输入、输出关系,但是类组件由于有组件自己的内部状态,所以其输出就由 propsstate 决定,类组件的输入、输出关系就不再那么纯洁。同时也会带来下列问题:

  1. 状态逻辑难以复用。很多类组件都有一些类似的状态逻辑,但是为了重用这些状态逻辑,社区提出了 render props 或者 hoc 这些方案,但是这两种模式对组件的侵入性太强。另外,会产生组件嵌套地狱的问题。
  2. 大多数开发者在编写组件时,不管这个组件有木有内部状态,会不会执行生命周期函数,都会将组件编写成类组件,后续迭代可能增加了内部状态,又增加了副作用处理,又在组件中调用了一些生命周期函数,文件代码行数日益增多,最后导致组件中充斥着无法管理的混乱的状态逻辑代码和各种副作用,各种状态逻辑散落在实例方法和生命周期方法中,维护性变差,拆分更是难上加难。
  3. 在类组件中,需要开发者额外去关注 this 问题,事件监听器的添加和移除等等。

State Hook

state hook 提供了一种可以在 function component 中添加状态的方式。通过 state hook,可以抽取状态逻辑,使组件变得可测试,可重用。开发者可以在不改变组件层次结构的情况下,去重用状态逻辑。更好的实现关注点分离。

一个简单的使用 useState 栗子

import React, { useState } from "react";const StateHook &#61; () &#61;> {const [count, setCount] &#61; useState(0);return (<div><p>you clicked {count} timesp><button type&#61;"button" onClick&#61;{() &#61;> setCount(count &#43; 1)}>click mebutton>div>);
};

几点说明&#xff1a;

  1. useState 推荐一种更加细粒度的控制状态的方式&#xff0c;即一个状态对应一个状态设置函数&#xff0c;其接受的参数将作为这个状态的初始值。其返回一个长度为2的元组&#xff0c;第一项为当前状态&#xff0c;第二项为更新函数。

  2. useState 的执行顺序在每一次更新渲染时必须保持一致&#xff0c;否则多个 useState 调用将不会得到各自独立的状态&#xff0c;也会造成状态对应混乱。比如在条件判断中使用 hook&#xff0c;在循环&#xff0c;嵌套函数中使用 hook&#xff0c;都会造成 hook 执行顺序不一致的问题。最后导致状态的混乱。另外&#xff0c;所有的状态声明都应该放在函数顶部&#xff0c;首先声明。

  3. useStatesetState 的区别

useStatesetState 进行覆盖式更新&#xff0c;而 setState 则将状态进行合并式更新。

一个不正确的栗子

import React, { useState, ChangeEvent } from "react";const UserForm &#61; () &#61;> {const [state, setUser] &#61; useState({ name: "", email: "" });const { name, email } &#61; state;const handleNameChange &#61; (event: ChangeEvent) &#61;> {const { target: { value: name } } &#61; event;// 这里不可以单独的设置某一个字段 新的状态必须与初始的状态类型保持一致// 如果只设置了其中一个字段&#xff0c;编译器会报错&#xff0c;同时其余的字段也会丢失setUser({ name, email });};const handleEmailChange &#61; (event: ChangeEvent) &#61;> {const { target: { value: email } } &#61; event;// 这里不可以单独的设置某一个字段 新的状态必须与初始的状态类型保持一致setUser({ name, email });};return (<>);
}

正确的做法

import React, { useState, ChangeEvent } from "react";const UserForm &#61; () &#61;> {// 一个状态对应一个状态更新函数const [name, setName] &#61; useState("");const [email, setEmail] &#61; useState("");const handleNameChange &#61; (event: ChangeEvent) &#61;> {const { target: { value: name } } &#61; event;// hear could do some validationsetName(name);};const handleEmailChange &#61; (event: ChangeEvent) &#61;> {const { target: { value: email } } &#61; event;// hear could do some validationsetEmail(email);};return (<>);
}

Effect Hook

数据获取&#xff0c;设置订阅&#xff0c;手动的更改 DOM&#xff0c;都可以称为副作用&#xff0c;可以将副作用分为两种&#xff0c;一种是需要清理的&#xff0c;另外一种是不需要清理的。比如网络请求&#xff0c;DOM 更改&#xff0c;日志这些副作用都不要清理。而比如定时器&#xff0c;事件监听。

一个简单使用 effect hook 去修改文档标题的栗子。

import React, { useState, useEffect } from "react";const effectHook &#61; () &#61;> {const [count, setCount] &#61; useState(0);useEffect(() &#61;> {document.title &#61; &#96;you clicked ${count} times&#96;;}, [count]);return (<div><p>you clicked {count} timesp><button type&#61;"button" onClick&#61;{() &#61;> setCount(count &#43; 1)}>click mebutton>div>);
};

在调用 useEffect 后&#xff0c;相当于告诉 React 在每一次组件更新完成渲染后&#xff0c;都调用传入 useEffect 中的函数&#xff0c;包括初次渲染以及后续的每一次更新渲染。

几点说明&#xff1a;

  1. useEffect(effectCallback: () &#61;> void, deps: any[]) 接收两个参数&#xff0c;第二个参数依赖项是可选的&#xff0c;表示这个 effect 要依赖哪些值。
  2. 有时候我们并不想每次渲染 effect 都执行&#xff0c;只有某些值发生变化才去执行 effect&#xff0c;这个时候我们可以指定这个 effect 的依赖列表&#xff0c;可以是一个也可以几个&#xff0c;当其中列表中的某一个值发生变化&#xff0c;effect 才会执行。
  3. 第一个参数的返回值&#xff0c;会在组件卸载时执行&#xff0c;相当于 componentWillUnmount&#xff0c;可以清理定时器&#xff0c;移除事件监听&#xff0c;取消一些订阅。
  4. 当第二个参数为一个空数组时&#xff0c;相当于 componentDidMount 和 componentWillUnmount&#xff0c;表明这个 effect 没有任何依赖&#xff0c;只在首次渲染时执行。

Custom Hook

也可以使用 useEffectuseState 实现自定义 hook。

一个给 DOM 元素添加事件监听器的栗子。

import { useRef, useEffect } from "react";type EventType &#61; keyof HTMLElementEventMap;
type Handler &#61; (event: Event) &#61;> void;const useEventListener &#61; (eventName: EventType,handler: Handler,element: EventTarget &#61; document
) &#61;> {// 这里使用 &#96;useRef&#96; 来保存传入的监听器&#xff0c;// 在监听器变更后&#xff0c;去更新 &#96;useRef&#96; 返回的对象的 &#96;current&#96; 属性const saveHandler &#61; useRef();useEffect(() &#61;> {saveHandler.current &#61; handler;}, [handler]);useEffect(() &#61;> {const supported &#61; element && element.addEventListener;if (!supported) {return;}const listener: Handler &#61; (event: Event) &#61;> (saveHandler.current as Handler)(event);element.addEventListener(eventName, listener);return () &#61;> {element.removeEventListener(eventName, listener);};}, [eventName, element]);
}

一个使用 useReducer 来实现加、减计数器的栗子。这里虽然使用 useReducer 创建了类似 redux 的 功能&#xff0c;但是如果有多个组件都引用了这个 hook&#xff0c;那么这个 hook 提供的状态是相互独立、互不影响的&#xff0c;即 useReducer 只提供了状态管理&#xff0c;但是并没有提供数据持久化的功能。redux 却提供了一种全局维护同一个数据源的机制。所以可以利用 useReducerContext 来实现数据持久化的功能。

import React, { useReducer } from "react";const INCREMENT &#61; "increment";
const DECREMENT &#61; "decrement";const initHandle &#61; (initCount) &#61;> {return { count: initCount };
};const reducer &#61; (state, action) &#61;> {switch (action.type) {case INCREMENT:return { count: state.count &#43; 1 };case DECREMENT:return { count: state.count - 1 };case "reset":return { count: action.payload };default:return state;}
};const Counter &#61; ({ initialCount }) &#61;> {const [state, dispatch] &#61; useReducer(reducer, initialCount, initHandle);const { count } &#61; state;return (<div>Counter: {count}<button type&#61;"button" onClick&#61;{() &#61;> dispatch({ type: "reset", payload: initialCount })}>Resetbutton><button type&#61;"button" onClick&#61;{() &#61;> dispatch({ type: INCREMENT })}>&#43;button><button type&#61;"button" onClick&#61;{() &#61;> dispatch({ type: DECREMENT })}>-button>jdiv>);
};

一个对封装数据请求栗子。

import { useState, useEffect } from "react";
import axios, { AxiosRequestConfig } from "axios";interface RequestError {error: null | boolean;message: string;
}const requestError: RequestError &#61; {error: null,message: "",
};/*** &#64;param url request url* &#64;param initValue if initValue changed, the request will send again* &#64;param options request config data** &#64;returns a object contains response&#39;s data, request loading and request error*/
const useFetchData &#61; (url: string, initValue: any, options: AxiosRequestConfig &#61; {}) &#61;> {const [data, saveData] &#61; useState();const [loading, updateLoading] &#61; useState(false);const [error, updateError] &#61; useState(requestError);let ignore &#61; false;const fetchData &#61; async () &#61;> {updateLoading(true);const response &#61; await axios(url, options);if (!ignore) saveData(response.data);updateLoading(false);};useEffect(() &#61;> {try {fetchData();} catch (error) {updateError({ error: true, message: error.message });}return () &#61;> {ignore &#61; true;};}, [initValue]);return { data, loading, error };
};export { useFetchData };

Rules of Hook

随来 hooks 带来了新的组件编写范式&#xff0c;但是下面两条规则还是要开发者注意的。

  1. 在顶部使用 hook&#xff0c;不要使用 hook 在条件判断&#xff0c;循环&#xff0c;嵌套函数。
  2. 只在 function component 中使用 hook&#xff0c;或者自定义 hook 中使用 hook, 不要在常规的 Javascript 函数中使用 hook

新的问题

hooks 的带来&#xff0c;虽然解决之前存在的一些问题&#xff0c;但是也带来了新的问题。

  1. 异常捕获。之前的版本中&#xff0c;我们可以用 componentDidCatch 来捕获组件作用域内的异常&#xff0c;做一些提示。但是在 hooks 中 &#xff0c;我们只能使用 try {} catch(){} &#96; 去捕获&#xff0c;使用姿势也比较别扭。
  2. 一个组件若有状态&#xff0c;则状态一旦改变&#xff0c;所有的子组件需要重新渲染。所以一个有状态的组件&#xff0c;应该是没有子组件的。即 有状态的组件不做渲染&#xff0c;有渲染的组件没有状态
  3. 状态变更的函数不支持回调。this.setState() 中支持第二个参数&#xff0c;允许我们在状态变更后&#xff0c;传入回调函数做一些其他事情。但是 useState 不支持。详见。

链接**

making-sense-of-react-hooks

rehooks

awesome-react-hooks

如何使用useEffect来获取数据

hooks 是如何工作的

更多关于 hooks 的讨论

转:https://juejin.im/post/5cccf38151882541ac5c4e5a



推荐阅读
  • 本文讨论了一个关于cuowu类的问题,作者在使用cuowu类时遇到了错误提示和使用AdjustmentListener的问题。文章提供了16个解决方案,并给出了两个可能导致错误的原因。 ... [详细]
  • 在重复造轮子的情况下用ProxyServlet反向代理来减少工作量
    像不少公司内部不同团队都会自己研发自己工具产品,当各个产品逐渐成熟,到达了一定的发展瓶颈,同时每个产品都有着自己的入口,用户 ... [详细]
  • 前景:当UI一个查询条件为多项选择,或录入多个条件的时候,比如查询所有名称里面包含以下动态条件,需要模糊查询里面每一项时比如是这样一个数组条件:newstring[]{兴业银行, ... [详细]
  • 向QTextEdit拖放文件的方法及实现步骤
    本文介绍了在使用QTextEdit时如何实现拖放文件的功能,包括相关的方法和实现步骤。通过重写dragEnterEvent和dropEvent函数,并结合QMimeData和QUrl等类,可以轻松实现向QTextEdit拖放文件的功能。详细的代码实现和说明可以参考本文提供的示例代码。 ... [详细]
  • android listview OnItemClickListener失效原因
    最近在做listview时发现OnItemClickListener失效的问题,经过查找发现是因为button的原因。不仅listitem中存在button会影响OnItemClickListener事件的失效,还会导致单击后listview每个item的背景改变,使得item中的所有有关焦点的事件都失效。本文给出了一个范例来说明这种情况,并提供了解决方法。 ... [详细]
  • 本文介绍了UVALive6575题目Odd and Even Zeroes的解法,使用了数位dp和找规律的方法。阶乘的定义和性质被介绍,并给出了一些例子。其中,部分阶乘的尾零个数为奇数,部分为偶数。 ... [详细]
  • 也就是|小窗_卷积的特征提取与参数计算
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了卷积的特征提取与参数计算相关的知识,希望对你有一定的参考价值。Dense和Conv2D根本区别在于,Den ... [详细]
  • springmvc学习笔记(十):控制器业务方法中通过注解实现封装Javabean接收表单提交的数据
    本文介绍了在springmvc学习笔记系列的第十篇中,控制器的业务方法中如何通过注解实现封装Javabean来接收表单提交的数据。同时还讨论了当有多个注册表单且字段完全相同时,如何将其交给同一个控制器处理。 ... [详细]
  • [大整数乘法] java代码实现
    本文介绍了使用java代码实现大整数乘法的过程,同时也涉及到大整数加法和大整数减法的计算方法。通过分治算法来提高计算效率,并对算法的时间复杂度进行了研究。详细代码实现请参考文章链接。 ... [详细]
  • 本文介绍了南邮ctf-web的writeup,包括签到题和md5 collision。在CTF比赛和渗透测试中,可以通过查看源代码、代码注释、页面隐藏元素、超链接和HTTP响应头部来寻找flag或提示信息。利用PHP弱类型,可以发现md5('QNKCDZO')='0e830400451993494058024219903391'和md5('240610708')='0e462097431906509019562988736854'。 ... [详细]
  • 本文介绍了一个题目的解法,通过二分答案来解决问题,但困难在于如何进行检查。文章提供了一种逃逸方式,通过移动最慢的宿管来锁门时跑到更居中的位置,从而使所有合格的寝室都居中。文章还提到可以分开判断两边的情况,并使用前缀和的方式来求出在任意时刻能够到达宿管即将锁门的寝室的人数。最后,文章提到可以改成O(n)的直接枚举来解决问题。 ... [详细]
  • Java学习笔记之面向对象编程(OOP)
    本文介绍了Java学习笔记中的面向对象编程(OOP)内容,包括OOP的三大特性(封装、继承、多态)和五大原则(单一职责原则、开放封闭原则、里式替换原则、依赖倒置原则)。通过学习OOP,可以提高代码复用性、拓展性和安全性。 ... [详细]
  • 本文详细介绍了如何使用MySQL来显示SQL语句的执行时间,并通过MySQL Query Profiler获取CPU和内存使用量以及系统锁和表锁的时间。同时介绍了效能分析的三种方法:瓶颈分析、工作负载分析和基于比率的分析。 ... [详细]
  • Ihavethefollowingonhtml我在html上有以下内容<html><head><scriptsrc..3003_Tes ... [详细]
  • 本文介绍了如何使用Express App提供静态文件,同时提到了一些不需要使用的文件,如package.json和/.ssh/known_hosts,并解释了为什么app.get('*')无法捕获所有请求以及为什么app.use(express.static(__dirname))可能会提供不需要的文件。 ... [详细]
author-avatar
哈行小DWW_421
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有