简介
Hook 是 React 16.8 的新增特性,它让你在不写 类组件 class component 时也能使用 state 以及其他的 React 特性。
1.为什么要使用Hooks
(1)组件之间复用状态逻辑很难
(2)复杂组件变的难以理解
(3)难以理解的class
为了解决这些问题,Hook使你在非class的情况下可以使用更多的React特性。从概念上讲,React组件一直更像是函数。而Hook则拥抱了函数,同时也没有牺牲React的精神原则。Hook提供了问题的解决方案,无需学习复杂的函数式或响应式编程技术。
2.React中的Hook
2.1 UseState — 用于定义组件的state,对标类组件中的this.state功能
const [state, setState] = useState(initialState);
const [状态,修改该状态的方法] = useState(初始值);
复制代码
在同一个组件中可以 使用useState定义多个状态
useState返回的setState方法,不会进行对象合并
useState返回的setState方法同样是异步方法,会被批处理所监管
function App(props){
const [count,setCount] = useState(0);
const [nub,setNub] = useState(10);
return <div>
<p>count:{count}</p>
<button onClick={()=>{
setCount(count+1);
}}>递增</button>
<p>nub:{nub}</p>
<button onClick={()=>{
setNub(nub+1);
}}>递增</button>
</div>
}
复制代码
hooks 使用时注意事项
1. hooks 只能在 react 函数中使用(函数式组件,自定义 hook)
2. hooks 在使用必须保证其调用顺序,不能将hooks的调用,放在 if for 等流程控制语句中,也不能将 hooks 调用放在子函数中。hooks 只能放在 函数组件(或自定义 hook)的最外层调用(必须保证其调用顺序: 就是每次执行该函数式,hooks的调用顺序,不会改变)
复制代码
为什么不要在循环、条件或嵌套函数中调用Hook?
Hooks的设计是基于数组实现。在调用时按顺序加入数组中,如果使用循环、条件、嵌套函数很有可能导致数组取值错位,执行错误的Hook.实质上React的源码里并不是数组而是链表t
复制代码
为什么useState返回的是数组而不是对象?
如果useState返回数组,那么你可以顺便对数组中的变量命名,代码看起来也比较干净.而如果是对象的话返回的值必须和useState内部实现返回的对象同名,这样你只能在function component中使用一次,想要多次使用useState必须得重命名返回值
复制代码
2.2 useEffect 副作用(DOM操作,数据请求) hook – 主要用来处理组件中的作用类型,用于替代生命周期
useEffect(() => {
effect: 副作用函数
return () => {
cleanup 清理函数
}
}, [input]) input: 依赖参数
复制代码
挂载阶段: 从上到下执行函数组件,如果碰到 useEffect 就将其中的 effect 存储到一个队列中,当组件挂载完成之后,按照队列顺序执行 effect 函数,
并获取 cleanup 函数 存贮至一个新的队列
更新阶段: 从上到下执行函数组件,如果碰到 useEffect 就将其中的 effect 存储到一个队列中,当组件更新完成之后,会将之前存储的 cleanup 函数队列
按照顺序执行, 然后 执行 effect 队列,并获取 cleanup 函数 存贮至一个新的队列。在更新阶段会观察依赖参数的值有没有发生变化,如果没有变化就
不执行对应的 cleanup 和 effect
卸载阶段: 找到之前存储的 cleanup 函数队列,依次执行
依赖参数:
1. null 组件每次更新都执行
2. [] 组件更新时不执行
3. [依赖参数,依赖参数2……] 只要有一个依赖参数有变化就执行
复制代码
2.3 useLayoutEffect
useLayoutEffect主要在useEffect函数结果不满意时才被用到,一般的经验是当处理dom改变带来的副作用才会被用到,该hook执行时,浏览器并未对dom进行渲染,较useEffect执行要早
useEffect和useLayoutEffect的差异?
1.useEffect和useLayoutEffect在Mount和Update阶段执行方法一样,传参不一样
2.useEffect异步执行,而useLayoutEffect同步执行
3.Effect用HookPassive标记useEffect,用HookLayout标记useLayoutEEffect
复制代码
2.4 useRef 类似于createRef
除了可以保存实例之外,还可以保存组件更新前的一些数据
当 ref 存储的是数据,数据不会随着组件的更新,而自动更新
复制代码
function InputFocus() {
const inputEl = useRef(null);
const onButtonClick = () => {
inputEl.current.focus();
};
return (
<>
<input ref={inputEl} type="text" />
<button onClick={onButtonClick}>Focus the input</button>
</>
);
}
复制代码
2.5 useImperativeHandle
useImperativeHandle(ref, createHandle, [deps])
复制代码
useImperativeHandle可以让你在使用ref时自定义暴露给父组件的实例值。在大多数情况下,应当避免使用ref这样的命令式代码。useImperativeHandle应当与fowardRed一起使用
1.ref:定义 current 对象的 ref createHandle:一个函数,返回值是一个对象,即这个 ref 的 current
2.对象 [deps]:即依赖列表,当监听的依赖发生变化,useImperativeHandle 才会重新将子组件的实例属性输出到父组件
3.ref 的 current 属性上,如果为空数组,则不会重新输出。
复制代码
import { useRef, useImperativeHandle, forwardRef } from "react";
function FancyInput(props, ref) {
useImperativeHandle(ref, () => ({
save: () => {
console.log("保存");
},
}));
return <input />;
}
FancyInput = forwardRef(FancyInput);
function App() {
const inputRef = useRef(null);
return (
<>
<span onClick={() => inputRef.current.save()}>保存</span>
<FancyInput ref={inputRef} />
</>
);
}
export default App;
复制代码
2.6 useMemo
当依赖参数,有变化时,执行相应函数,并返回函数的返回值
import { useState, useMemo } from "react";
function App() {
const [count, setCount] = useState(1);
const applePrice = useMemo(() => {
return count * 38;
}, [count]);
return (
<div>
<p>
count:{count} 斤车厘子,单价:38元,总价{applePrice}元
</p>
<button
onClick={() => {
setCount(count + 1);
}}
>
count-递增
</button>
</div>
);
}
export default App;
复制代码
2.7 useCallback
返回一个函数,当监听的数据发生变化才会执行回调函数
import { useState, useCallback } from "react";
function App() {
const [count1, setCount1] = useState(1);
const [count2, setCount2] = useState(2);
const [count3, setCount3] = useState(3);
const handleBtn1 = () => {
setCount1(count1 + 1);
}
const handleBtn2 = useCallback(() => {
setCount2(count2 + 2);
}, [count2]);
return (
<div>
<h3>{count1}</h3>
<button
onClick={handleBtn1}
>
count1
</button>
<h3>{count2}</h3>
<button onClick={handleBtn2}>count2</button>
<h3>{count3}</h3>
<button
onClick={() => {
setCount3(count3 + 3);
}}
>
count3
</button>
</div>
);
}
export default App;
复制代码
2.8 useContext 作用就是对它所包含的组件树提供全局共享数据的一种技术
首先创建context.js
import { createContext } from "react";
export const MyContext = createContext({});
复制代码
父组件
import React, { useState } from "react";
import Counter from "./count";
import { MyContext } from "./context";
const App = () => {
const [count, setCount] = useState(0);
return (
<div>
<p>父组件点击数量:{count}</p>
<button onClick={() => setCount(count + 1)}>{"点击+1"}</button>
<MyContext.Provider value={count}>
<Counter />
</MyContext.Provider>
</div>
);
};
export default App;
复制代码
子组件
import React, { useContext } from "react";
import { MyContext } from "./context";
function Counter(props) {
const count = useContext(MyContext);
return (
<>
<p>子组件获得的点击数量:{count}</p>
</>
);
}
export default Counter;
复制代码
2.9.自定义Hooks
下面举个简单的例子:使用自定义Hooks获取state或props上一次的值
自定义Hooks获取state或props上一次的值
function usePreVal(val) {
const preValue = useRef();
useEffect(() => {
preValue.current = val;
}, [val]);
return preValue.current;
}
复制代码
自定义Hooks的使用
import { useState, useRef, useEffect } from "react";
function App() {
const [count, setCount] = useState(1);
const [num, setNum] = useState(5);
const preCount = usePreVal(count);
const preNum = usePreVal(num);
return (
<>
<div>
最新的{count}之前的{preCount}
</div>
<button
onClick={() => {
setCount(count + 1);
}}
>
count递增
</button>
<div>
最新的{num}之前的{preNum}
</div>
<button
onClick={() => {
setNum(num + 5);
}}
>
num递增
</button>
</>
);
}
export default App;
复制代码