摘要:比如在条件判断中使用,在循环,嵌套函数中使用,都会造成执行顺序不一致的问题。而比如定时器,事件监听。第一个参数的返回值,会在组件卸载时执行,相当于,可以清理定时器,移除事件监听,取消一些订阅。
什么是 Hooks?
不通过编写类组件的情况下,可以在组件内部使用状态(state) 和其他 React 特性(生命周期,context)的技术Hooks 为什么会出现
在之前的 React 版本中,组件分为两种:函数式组件(或无状态组件(StatelessFunctionComponent))和类组件,而函数式组件是一个比较的纯洁的 props => UI 的输入、输出关系,但是类组件由于有组件自己的内部状态,所以其输出就由 props 和 state 决定,类组件的输入、输出关系就不再那么纯洁。同时也会带来下列问题:
状态逻辑难以复用。很多类组件都有一些类似的状态逻辑,但是为了重用这些状态逻辑,社区提出了 render props 或者 hoc 这些方案,但是这两种模式对组件的侵入性太强。另外,会产生组件嵌套地狱的问题。
大多数开发者在编写组件时,不管这个组件有木有内部状态,会不会执行生命周期函数,都会将组件编写成类组件,后续迭代可能增加了内部状态,又增加了副作用处理,又在组件中调用了一些生命周期函数,文件代码行数日益增多,最后导致组件中充斥着无法管理的混乱的状态逻辑代码和各种副作用,各种状态逻辑散落在实例方法和生命周期方法中,维护性变差,拆分更是难上加难。
在类组件中,需要开发者额外去关注 this 问题,事件监听器的添加和移除等等。
State Hookstate hook 提供了一种可以在 function component 中添加状态的方式。通过 state hook,可以抽取状态逻辑,使组件变得可测试,可重用。开发者可以在不改变组件层次结构的情况下,去重用状态逻辑。更好的实现关注点分离。
一个简单的使用 useState 栗子
import React, { useState } from "react"; const StateHook = () => { const [count, setCount] = useState(0); return (); };you clicked {count} times
几点说明:
useState 推荐一种更加细粒度的控制状态的方式,即一个状态对应一个状态设置函数,其接受的参数将作为这个状态的初始值。其返回一个长度为2的元组,第一项为当前状态,第二项为更新函数。
useState 的执行顺序在每一次更新渲染时必须保持一致,否则多个 useState 调用将不会得到各自独立的状态,也会造成状态对应混乱。比如在条件判断中使用 hook,在循环,嵌套函数中使用 hook,都会造成 hook 执行顺序不一致的问题。最后导致状态的混乱。另外,所有的状态声明都应该放在函数顶部,首先声明。
useState 和 setState 的区别
useState 将 setState 进行覆盖式更新,而 setState 则将状态进行合并式更新。
一个不正确的栗子
import React, { useState, ChangeEvent } from "react"; const UserForm = () => { const [state, setUser] = useState({ name: "", email: "" }); const { name, email } = state; const handleNameChange = (event: ChangeEvent) => { const { target: { value: name } } = event; // 这里不可以多带带的设置某一个字段 新的状态必须与初始的状态类型保持一致 // 如果只设置了其中一个字段,编译器会报错,同时其余的字段也会丢失 setUser({ name, email }); }; const handleEmailChange = (event: ChangeEvent ) => { const { target: { value: email } } = event; // 这里不可以多带带的设置某一个字段 新的状态必须与初始的状态类型保持一致 setUser({ name, email }); }; return ( <> > ); }
正确的做法
import React, { useState, ChangeEvent } from "react"; const UserForm = () => { // 一个状态对应一个状态更新函数 const [name, setName] = useState(""); const [email, setEmail] = useState(""); const handleNameChange = (event: ChangeEventEffect Hook) => { const { target: { value: name } } = event; // hear could do some validation setName(name); }; const handleEmailChange = (event: ChangeEvent ) => { const { target: { value: email } } = event; // hear could do some validation setEmail(email); }; return ( <> > ); }
数据获取,设置订阅,手动的更改 DOM,都可以称为副作用,可以将副作用分为两种,一种是需要清理的,另外一种是不需要清理的。比如网络请求,DOM 更改,日志这些副作用都不要清理。而比如定时器,事件监听。
一个简单使用 effect hook 去修改文档标题的栗子。
import React, { useState, useEffect } from "react"; const effectHook = () => { const [count, setCount] = useState(0); useEffect(() => { document.title = `you clicked ${count} times`; }, [count]); return (); };you clicked {count} times
在调用 useEffect 后,相当于告诉 React 在每一次组件更新完成渲染后,都调用传入 useEffect 中的函数,包括初次渲染以及后续的每一次更新渲染。
几点说明:
useEffect(effectCallback: () => void, deps: any[]) 接收两个参数,第二个参数依赖项是可选的,表示这个 effect 要依赖哪些值。
有时候我们并不想每次渲染 effect 都执行,只有某些值发生变化才去执行 effect,这个时候我们可以指定这个 effect 的依赖列表,可以是一个也可以几个,当其中列表中的某一个值发生变化,effect 才会执行。
第一个参数的返回值,会在组件卸载时执行,相当于 componentWillUnmount,可以清理定时器,移除事件监听,取消一些订阅。
当第二个参数为一个空数组时,相当于 componentDidMount 和 componentWillUnmount,表明这个 effect 没有任何依赖,只在首次渲染时执行。
Custom Hook也可以使用 useEffect 和 useState 实现自定义 hook。
一个给 DOM 元素添加事件监听器的栗子。
import { useRef, useEffect } from "react"; type EventType = keyof HTMLElementEventMap; type Handler = (event: Event) => void; const useEventListener = ( eventName: EventType, handler: Handler, element: EventTarget = document ) => { // 这里使用 `useRef` 来保存传入的监听器, // 在监听器变更后,去更新 `useRef` 返回的对象的 `current` 属性 const saveHandler = useRef(); useEffect(() => { saveHandler.current = handler; }, [handler]); useEffect(() => { const supported = element && element.addEventListener; if (!supported) { return; } const listener: Handler = (event: Event) => (saveHandler.current as Handler)(event); element.addEventListener(eventName, listener); return () => { element.removeEventListener(eventName, listener); }; }, [eventName, element]); }
一个使用 useReducer 来实现加、减计数器的栗子。这里虽然使用 useReducer 创建了类似 redux 的 功能,但是如果有多个组件都引用了这个 hook,那么这个 hook 提供的状态是相互独立、互不影响的,即 useReducer 只提供了状态管理,但是并没有提供数据持久化的功能。redux 却提供了一种全局维护同一个数据源的机制。所以可以利用 useReducer 和 Context 来实现数据持久化的功能。
import React, { useReducer } from "react"; const INCREMENT = "increment"; const DECREMENT = "decrement"; const initHandle = (initCount) => { return { count: initCount }; }; const reducer = (state, action) => { switch (action.type) { case INCREMENT: return { count: state.count + 1 }; case DECREMENT: return { count: state.count - 1 }; case "reset": return { count: action.payload }; default: return state; } }; const Counter = ({ initialCount }) => { const [state, dispatch] = useReducer(reducer, initialCount, initHandle); const { count } = state; return (Counter: {count} j); };
一个对封装数据请求栗子。
import { useState, useEffect } from "react"; import axios, { AxiosRequestConfig } from "axios"; interface RequestError { error: null | boolean; message: string; } const requestError: RequestError = { error: null, message: "", }; /** * @param url request url * @param initValue if initValue changed, the request will send again * @param options request config data * * @returns a object contains response"s data, request loading and request error */ const useFetchData = (url: string, initValue: any, options: AxiosRequestConfig = {}) => { const [data, saveData] = useState(); const [loading, updateLoading] = useState(false); const [error, updateError] = useState(requestError); let ignore = false; const fetchData = async () => { updateLoading(true); const response = await axios(url, options); if (!ignore) saveData(response.data); updateLoading(false); }; useEffect(() => { try { fetchData(); } catch (error) { updateError({ error: true, message: error.message }); } return () => { ignore = true; }; }, [initValue]); return { data, loading, error }; }; export { useFetchData };Rules of Hook
随来 hooks 带来了新的组件编写范式,但是下面两条规则还是要开发者注意的。
在顶部使用 hook,不要使用 hook 在条件判断,循环,嵌套函数。
只在 function component 中使用 hook,或者自定义 hook 中使用 hook, 不要在常规的 JavaScript 函数中使用 hook
新的问题hooks 的带来,虽然解决之前存在的一些问题,但是也带来了新的问题。
异常捕获。之前的版本中,我们可以用 componentDidCatch 来捕获组件作用域内的异常,做一些提示。但是在 hooks 中 ,我们只能使用 try {} catch(){} ` 去捕获,使用姿势也比较别扭。
一个组件若有状态,则状态一旦改变,所有的子组件需要重新渲染。所以一个有状态的组件,应该是没有子组件的。即 有状态的组件不做渲染,有渲染的组件没有状态。
状态变更的函数不支持回调。this.setState() 中支持第二个参数,允许我们在状态变更后,传入回调函数做一些其他事情。但是 useState 不支持。详见。
参考链接
making-sense-of-react-hooks
rehooks
awesome-react-hooks
如何使用useEffect来获取数据
hooks 是如何工作的
更多关于 hooks 的讨论
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/104060.html
摘要:前言一直对这个新特性非常感兴趣,终于今天有时间,花了大半天时间,把的官方教程过了一遍,收获颇多,惊叹这个新特性真好用,以后开发用这个怕是要起飞了 前言:一直对这个新特性非常感兴趣,终于今天有时间,花了大半天时间,把 Hooks的官方教程过了一遍,收获颇多,惊叹这个新特性真 TM 好用,以后开发用这个怕是要起飞了
摘要:使用完成副作用操作,赋值给的函数会在组件渲染到屏幕之后。如此很容易产生,并且导致逻辑不一致。同时,这也是很多人将与状态管理库结合使用的原因之一。当我们通过的第二个数组类型参数,指明当前的依赖,就能避免不相关的执行开销了。 前言 本文内容大部分参考了 overreacted.io 博客一文,同时结合 React Hook 官方 文章,整理并归纳一些笔记和输出个人的一些理解 什么是 Hoo...
摘要:的来源钩子,顾名思义,为了解决在函数组件中使用和生命周期,同时提高业务逻辑复用。函数组件等同于一个纯的专门用作渲染的函数,我们知道,在函数组件中,我们无法使用和生命周期,这也是为了解决的问题。 Hooks的来源 Hooks => 钩子,顾名思义,为了解决在函数组件(Function Component)中使用state和生命周期,同时提高业务逻辑复用。 Function Co...
摘要:基于与网易云音乐开发,技术栈主要是目前主要是着重小程序端的展示,主要也是借此项目强化下上述几个技术栈的使用,通过这个项目也可以帮助你快速使用开发一个属于你自己的小程序地址,感兴趣的话可以关注下,功能会进行持续完善快速开始首先需要在目录下 基于Taro与网易云音乐api开发,技术栈主要是:typescript+taro+taro-ui+redux,目前主要是着重小程序端的展示,主要也是借...
摘要:此优化有助于避免在每个渲染上进行昂贵的计算。同样也是一个函数,接受两个参数,第一个参数为函数,第二个参数为要比对的值,返回一个值。同理,第二个参数传入的值没有更新时,不会执行。以上代码的地址为初体验 什么是Hooks?Hooks是react即将推出的功能,它允许您在不编写类的情况下使用状态和其他React功能。我的理解就是可以用写无状态组件的方式去编写拥有状态的组件。遗憾的是,正式版1...
阅读 1772·2023-04-26 01:41
阅读 3074·2021-11-23 09:51
阅读 2735·2021-10-09 09:43
阅读 9025·2021-09-22 15:13
阅读 2453·2021-09-07 09:59
阅读 2626·2019-08-30 15:44
阅读 1134·2019-08-30 12:45
阅读 2619·2019-08-30 12:43