什么是 Hooks?
不经由过程编写类组件的情况下,能够在组件内部运用状况(state) 和其他 React 特征(性命周期,context)的手艺
Hooks 为何会涌现
在之前的 React 版本中,组件分为两种:函数式组件(或无状况组件(StatelessFunctionComponent))和类组件,而函数式组件是一个比较的贞洁的 props => UI
的输入、输出关联,然则类组件因为有组件本身的内部状况,所以其输出就由 props
和 state
决议,类组件的输入、输出关联就不再那末贞洁。同时也会带来以下题目:
- 状况逻辑难以复用。许多类组件都有一些相似的状况逻辑,然则为了重用这些状况逻辑,社区提出了
render props
或许hoc
这些计划,然则这两种形式对组件的侵入性太强。别的,会发生组件嵌套地狱的题目。 - 大多数开发者在编写组件时,不论这个组件有木有内部状况,会不会实行性命周期函数,都邑将组件编写成类组件,后续迭代能够增加了内部状况,又增加了副作用处置惩罚,又在组件中挪用了一些性命周期函数,文件代码行数日趋增加,末了致使组件中充溢着没法治理的杂沓的状况逻辑代码和种种副作用,种种状况逻辑散落在实例要领和性命周期要领中,保护性变差,拆分更是难上加难。
- 在类组件中,须要开发者分外去关注 this 题目,事宜监听器的增加和移除等等。
State Hook
state hook 供应了一种能够在 function component 中增加状况的体式格局。经由过程 state hook,能够抽取状况逻辑,使组件变得可测试,可重用。开发者能够在不转变组件条理构造的情况下,去重用状况逻辑。更好的完成关注点星散。
一个简朴的运用 useState
栗子
import React, { useState } from "react";
const StateHook = () => {
const [count, setCount] = useState(0);
return (
<div>
<p>you clicked {count} times</p>
<button type="button" onClick={() => setCount(count + 1)}>
click me
</button>
</div>
);
};
几点申明:
-
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<HTMLInputElement>) => {
const { target: { value: name } } = event;
// 这里不能够零丁的设置某一个字段 新的状况必需与初始的状况范例保持一致
// 假如只设置了个中一个字段,编译器会报错,同时其他的字段也会丧失
setUser({ name, email });
};
const handleEmailChange = (event: ChangeEvent<HTMLInputElement>) => {
const { target: { value: email } } = event;
// 这里不能够零丁的设置某一个字段 新的状况必需与初始的状况范例保持一致
setUser({ name, email });
};
return (
<>
<input value={name} onChange={handleNameChange} />
<input value={email} onChange={handleEmailChange} />
</>
);
}
准确的做法
import React, { useState, ChangeEvent } from "react";
const UserForm = () => {
// 一个状况对应一个状况更新函数
const [name, setName] = useState("");
const [email, setEmail] = useState("");
const handleNameChange = (event: ChangeEvent<HTMLInputElement>) => {
const { target: { value: name } } = event;
// hear could do some validation
setName(name);
};
const handleEmailChange = (event: ChangeEvent<HTMLInputElement>) => {
const { target: { value: email } } = event;
// hear could do some validation
setEmail(email);
};
return (
<>
<input value={name} onChange={handleNameChange} />
<input value={email} onChange={handleEmailChange} />
</>
);
}
Effect Hook
数据猎取,设置定阅,手动的变动 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 (
<div>
<p>you clicked {count} times</p>
<button type="button" onClick={() => setCount(count + 1)}>
click me
</button>
</div>
);
};
在挪用 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<Handler>();
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 (
<div>
Counter: {count}
<button type="button" onClick={() => dispatch({ type: "reset", payload: initialCount })}>
Reset
</button>
<button type="button" onClick={() => dispatch({ type: INCREMENT })}>
+
</button>
<button type="button" onClick={() => dispatch({ type: DECREMENT })}>
-
</button>j
</div>
);
};
一个对封装数据要求栗子。
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
不支撑。详见。
参考链接