热门标签 | 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



推荐阅读
  • Vue应用预渲染技术详解与实践 ... [详细]
  • 在深入研究 React 项目的过程中,特别是在探索 react-router 源码时,我发现了其中蕴含的中间件概念。这激发了我对中间件的进一步思考与整理。本文将详细探讨 Redux 中间件的原理及其在实际项目中的应用,帮助读者更好地理解和使用这一强大工具。通过具体示例和代码解析,我们将揭示中间件如何提升应用的状态管理和异步操作处理能力。 ... [详细]
  • 本文介绍了如何在 Vue 3 组合 API 中正确设置 setup() 函数的 TypeScript 类型,以避免隐式 any 类型的问题。 ... [详细]
  • javascript分页类支持页码格式
    前端时间因为项目需要,要对一个产品下所有的附属图片进行分页显示,没考虑ajax一张张请求,所以干脆一次性全部把图片out,然 ... [详细]
  • 开机自启动的几种方式
    0x01快速自启动目录快速启动目录自启动方式源于Windows中的一个目录,这个目录一般叫启动或者Startup。位于该目录下的PE文件会在开机后进行自启动 ... [详细]
  • 在PHP中如何正确调用JavaScript变量及定义PHP变量的方法详解 ... [详细]
  • 在尝试对 QQmlPropertyMap 类进行测试驱动开发时,发现其派生类中无法正常调用槽函数或 Q_INVOKABLE 方法。这可能是由于 QQmlPropertyMap 的内部实现机制导致的,需要进一步研究以找到解决方案。 ... [详细]
  • QT框架中事件循环机制及事件分发类详解
    在QT框架中,QCoreApplication类作为事件循环的核心组件,为应用程序提供了基础的事件处理机制。该类继承自QObject,负责管理和调度各种事件,确保程序能够响应用户操作和其他系统事件。通过事件循环,QCoreApplication实现了高效的事件分发和处理,使得应用程序能够保持流畅的运行状态。此外,QCoreApplication还提供了多种方法和信号槽机制,方便开发者进行事件的定制和扩展。 ... [详细]
  • 深入解析:React与Webpack配置进阶指南(第二部分)
    在本篇进阶指南的第二部分中,我们将继续探讨 React 与 Webpack 的高级配置技巧。通过实际案例,我们将展示如何使用 React 和 Webpack 构建一个简单的 Todo 应用程序,具体包括 `TodoApp.js` 文件中的代码实现,如导入 React 和自定义组件 `TodoList`。此外,我们还将深入讲解 Webpack 配置文件的优化方法,以提升开发效率和应用性能。 ... [详细]
  • 本文介绍了UUID(通用唯一标识符)的概念及其在JavaScript中生成Java兼容UUID的代码实现与优化技巧。UUID是一个128位的唯一标识符,广泛应用于分布式系统中以确保唯一性。文章详细探讨了如何利用JavaScript生成符合Java标准的UUID,并提供了多种优化方法,以提高生成效率和兼容性。 ... [详细]
  • 深入解析 Vue3 中的响应式 API:shallowReactive、shallowRef、triggerRef 和 customRef 的使用与原理
    深入解析 Vue3 中的响应式 API:shallowReactive、shallowRef、triggerRef 和 customRef 的使用与原理 ... [详细]
  • 深入解析Struts、Spring与Hibernate三大框架的面试要点与技巧 ... [详细]
  • 在处理大规模数据数组时,优化分页组件对于提高页面加载速度和用户体验至关重要。本文探讨了如何通过高效的分页策略,减少数据渲染的负担,提升应用性能。具体方法包括懒加载、虚拟滚动和数据预取等技术,这些技术能够显著降低内存占用和提升响应速度。通过实际案例分析,展示了这些优化措施的有效性和可行性。 ... [详细]
  • Kotlin协程中async和await的常见异常陷阱及正确的异常处理方法
    在Kotlin协程中,`async`和`await`是常用的异步编程工具,尤其是在与Jetpack组件结合时,能够显著简化Android开发中的异步任务处理。然而,不当使用这些工具可能会导致常见的异常陷阱,如未捕获的异常或异常传播问题。本文将深入探讨这些陷阱,并提供有效的异常处理方法,帮助开发者避免潜在的问题,确保应用的稳定性和可靠性。 ... [详细]
  • 为了评估精心优化的模型与策略在实际环境中的表现,Google对其实验框架进行了全面升级,旨在实现更高效、更精准和更快速的在线测试。新的框架支持更多的实验场景,提供更好的数据洞察,并显著缩短了实验周期,从而加速产品迭代和优化过程。 ... [详细]
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社区 版权所有