前言
Hook
是 React 16.8
的新增特性。它是完全可选的
,并且100%向后兼容
。它可以让你使用函数组件的方式,运用类组件以及 react 其他的一些特性,比如管理状态
、生命周期钩子
等。从概念上讲,React 组件一直更像是函数。而 Hook 则拥抱了函数,同时也没有牺牲 React 的精神原则。
优点:
1、代码可读性更强,原本同一块功能的代码逻辑被拆分在了不同的生命周期函数中,容易使开发者不利于维护和迭代,通过 React Hooks 可以将功能代码聚合,方便阅读维护。例如,每个生命周期中常常会包含一些不相关的逻辑。一般我们都会在 componentDidMount
和 componentDidUpdate
中获取数据
。但是,同一个 componentDidMount 中可能也包含很多其它的逻辑
,如设置事件监听
,而之后需在 componentWillUnmount 中清除。相互关联且需要对照修改的代码被进行了拆分,而完全不相关
的代码
却在同一个方法
中组合
在一起。如此很容易产生 bug,并且导致逻辑不一致
。
2、组件树层级变浅。在原本的代码中,我们经常使用 HOC/render props 等方式来复用组件的状态,增强功能等,无疑增加了组件树层数及渲染,在 React DevTools 中观察过 React 应用,你会发现由 providers
,consumers
,高阶组件
,render
props
等其他抽象层组成的组件会形成“嵌套地狱
”。而在 React Hooks 中,这些功能都可以通过强大的自定义的 Hooks 来实现。
3、不用再去考虑 this 的指向问题。在类组件中,你必须去理解 JavaScript 中 this 的工作方式。
缺点:
对一些钩子函数不支持。当下 v16.8 的版本中,还无法实现 getSnapshotBeforeUpdate
和 componentDidCatch
这两个在类组件中的生命周期函数。
Hook 规则
- 不在循环,条件或嵌套函数中调用 Hook, 确保总是在你的 React 函数的最顶层调用他们。
- 不在普通的 JavaScript 函数中调用 Hook,在 React 的函数组件或者自定义 Hook 中调用 Hook。
Hook API
名称 | 描述 |
---|---|
useState | 在函数组件中维护自己的状态 |
useEffect | 在函数组件中实现生命周期钩子函数 |
useContext | 用来处理多层级传递数据的方式,减少组件嵌套 |
useReducer | 跟react-redux的使用方式一样,算是提供一个 mini 的 Redux 版本 |
useCallback | 获得一个记忆函数,避免在某些情况下重新渲染子组件,用来做性能优化 |
useMemo | 获得一个记忆组件,和useCallback非常类似,它适用于返回确定的值 |
useRef | 生成对 DOM 对象的引用,它是一个真正的引用,而不是把值拷过去 |
useImperativeHandle | 透传ref,用于让父组件获取子组件内的引用 |
useLayoutEffect | 同步执行副作用,在页面完全渲染完成后,操作DOM |
useState
在类组件中,我们使用 this.state
来保存组件状态,并对其修改触发组件重新渲染。而在函数组件中,由于没有 this 这个黑魔法,可能通过 useState
来帮我们保存组件的状态。
- useState(),返回一个 state,以及更新 state 的函数。
- useState() 中第一个参数是值或者对象,初始渲染期间,返回的状态 (state) 是传入的第一个参数相同。
- 如果依赖于先前的state,在第二个参数中接收先前的 state,并返回一个更新后的值。
import React, { useState } from "react";
function App() {
const [count, setCount] = useState(0); //0是count的默认值
return (
<div className="App">
Count: {count}
<button onClick={() => setCount((preState=>preState+1))}>+</button>
<button onClick={() => setCount(0)}>还原</button>
</div>
);
}
注意:与类组件中的 setState 方法不同,useState 不会自动合并更新对象
。当第一个参数是一个对象时,你可以用函数式的 setState 结合展开运算符
来达到合并更新对象的效果。
import React, { useState } from "react";
function App1(){
const [obj,setObj]=useState({
name:'admin',
age:18
})
return(
<div>
姓名:{obj.name}-年龄:{obj.age}
<button onClick={()=>setObj({...obj,name:obj.name+"用户"})}>只修改姓名</button>
</div>
)
}
useEffect
语法:useEffect(fn,Array)
- 第一个参数传递函数,可以用来做一些副作用比如异步请求,修改外部参数等行为。
- 第二个参数是个数组,
数组中
的值发生变化
才会触发
useEffect 第一个参数中的函数。 - 如果第二个参数是个
空数组
的话,默认会在页面加载后执行一次
。 - 如果第一个参数有返回值,会在
组件销毁
或者调用函数前调用。 - 可以使用useEffect模拟
componentDidMount
、componentDidMount
和componentWillUnmount
钩子函数。
import React, { useState, useEffect } from "react";
function App(){
const [count, setCount] = useState(0);
useEffect(()=>{
//只要count有变化,就会执行这里
},[count])
useEffect(()=>{
//如果在下面没有参数的话,页面加载后执行,执行一次
return()=>{
//页面退出的时候执行
}
},[])
}
useContext
用来处理多层级传递数据的方式,使用 useContext 可以解决 Consumer 多状态嵌套的问题
import React, { useContext } from "react";
const colorContext = React.createContext("gray");
function Bar() {
const color = useContext(colorContext);
return <div>{color}</div>;
}
function Foo() {
return <Bar />;
}
function App() {
return (
<colorContext.Provider value={"red"}>
<Foo />
</colorContext.Provider>
);
}
useReducer
useReducer 这个 Hooks 在使用上几乎跟 React-Redux 一模一样,唯一缺少的就是无法使用 redux 提供的中间件,算是提供一个 mini 的 Redux 版本。
import React, { useReducer } from "react";
const initialState = {
count: 0
};
function reducer(state, action) {
switch (action.type) {
case "increment":
return { count: state.count + action.payload };
case "decrement":
return { count: state.count - action.payload };
default:
throw new Error();
}
}
function App() {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<>
Count: {state.count}
<button onClick={() => dispatch({ type: "increment", payload: 5 })}>
+
</button>
<button onClick={() => dispatch({ type: "decrement", payload: 5 })}>
-
</button>
</>
);
}
useCallback
通过 useCallback 获得一个记忆后的函数,避免函数组件在每次渲染的时候如果有传递函数的话,重新渲染子组件。用来做性能优化。
import React, { useCallback } from "react";
function App() {
const memoizedHandleClick = useCallback(() => {
console.log('Click happened')
}, []); // 空数组代表无论什么情况下该函数都不会发生改变
return <SomeComponent onClick={memoizedHandleClick}>Click Me</SomeComponent>;
}
useMemo
记忆组件,和useCallback类似,不同的是:useCallback 不会执行第一个参数函数,而是将它返回给你,而 useMemo 会执行第一个函数并且将函数执行结果返回给你。所以在前面的例子中,可以返回 handleClick 来达到存储函数的目的。
所以 useCallback 常用记忆事件函数,生成记忆后的事件函数并传递给子组件使用。而 useMemo 更适合经过函数计算得到一个确定的值,比如记忆组件。
import React, { useMemo } from "react";
function App() {
const memoizedHandleClick = useMemo(() => () => {
console.log('Click happened')
}, []); // 空数组代表无论什么情况下该函数都不会发生改变
return <SomeComponent onClick={memoizedHandleClick}>Click Me</SomeComponent>;
}
useRef
跟 createRef 类似,都可以用来生成对 DOM 对象的引用。不同点在于,它是一个真正的引用,而不是把值拷过去。
import React, { useState, useRef } from "react";
function App() {
let [name, setName] = useState("Nate");
let nameRef = useRef();
const submitButton = () => {
setName(nameRef.current.value);
};
return (
<div className="App">
<p>{name}</p>
<div>
<input ref={nameRef} type="text" />
<button type="button" onClick={submitButton}>
Submit
</button>
</div>
</div>
);
}
useImperativeHandle
透传ref,用于让父组件获取子组件内的引用。
import React, { useRef, useEffect, useImperativeHandle, forwardRef } from "react";
function ChildInputComponent(props, ref) {
const inputRef = useRef(null);
useImperativeHandle(ref, () => inputRef.current);
return (
<div>
<input type="text" name="child input" ref={inputRef} placeholder="我聚焦了"/>
<input type="text" name="child input1" placeholder="我没有聚焦"/>
</div>
)
}
const ChildInput = forwardRef(ChildInputComponent);
function App() {
const inputRef = useRef(null);
useEffect(() => {
inputRef.current.focus();
}, []);
return (
<div>
<ChildInput ref={inputRef} />
</div>
);
}
useLayoutEffect
大部分情况下,使用 useEffect 就可以帮我们处理组件的副作用,但是如果想要同步调用一些副作用
,比如对 DOM 的操作
,就需要使用 useLayoutEffect
,useLayoutEffect 中的副作用会在 DOM 更新之后同步执行。
import React, { useState, useEffect, useLayoutEffect } from "react";
function App() {
const [width, setWidth] = useState(0);
useLayoutEffect(() => {
const title = document.querySelector("#title");
const titleWidth = title.getBoundingClientRect().width;
console.log("useLayoutEffect");
if (width !== titleWidth) {
setWidth(titleWidth);
}
});
useEffect(() => {
console.log("useEffect");
});
return (
<div>
<h1 id="title">hello</h1>
<h2>{width}</h2>
</div>
);
}
在上面的例子中,useLayoutEffect 会在 render,DOM 更新之后同步触发函数,会优于 useEffect 异步触发函数。
参考资料:https://react.docschina.org/docs/hooks-reference.html
https://github.com/happylindz/blog/issues/19