在React v16.8之前,React函数组件又称为无状态组件。Hook是React v16.8新出的特性,它可以允许我们在函数组件中使用state以及其他的React特性。即有了Hooks之后,函数组件也可以拥有和类组件一样的特性,如组件内的状态,生命周期等。
React中的Hooks
useState就是最常用的Hooks之一
在函数组件中,通过useState实现函数内部维护state,参数state默认的值,返回值为一个数组。第一个值为当前的num,第二个值为更新num的函数setNum。例如:
const [output, setOutput] = useState(props.value.toString());
useState不能局部更新。即更改其中一个属性时,需要先把其他不改的属性先拷贝进来。
useEffect可以理解为after render。
useEffect 也是常用的Hooks之一
useEffect第一个参数接受一个回调函数,默认情况下,即不传第二个参数。useEffect会在第一次渲染和更新之后都会执行,相当于在componentDidMount和componentDidUpdate两个生命周期函数中执行第一个参数,即回调函数。当传入第二个参数时,只在第二个参数变化时执行回调函数。
useEffect(fn1,[])模拟componentDidMount;useEffect(fn2)模拟componentDidUpdate;useEffect(fn2,[n])也是模拟componentDidUpdate;useEffect(()=>{ return fn3 })模拟componentWillUnmount。
useReducer 是useEffect的复杂版/进阶版
用法:
1.需要分别声明初始值state和操作reducer。
2.然后 const [formData, dispatch] = useReducer(reducer, initFormData);
3.声明一个读接口formData和一个写接口dispatch.
4.调用时写法:dispatch({ type: "xxx" });
import React, { useReducer } from "react";
import ReactDOM from "react-dom";
const initFormData = {
name: "",
age: 18,
nationality: "汉族"
};
function reducer(state, action) {
switch (action.type) {
case "patch":
return { ...state, ...action.formData };
case "reset":
return initFormData;
default:
throw new Error('类型出错');
}
}
function App() {
const [formData, dispatch] = useReducer(reducer, initFormData);
// const patch = (key, value)=>{
// dispatch({ type: "patch", formData: { [key]: value } })
// }
const onSubmit = () => {};
const onReset = () => {
dispatch({ type: "reset" });
};
return (
<form onSubmit={onSubmit} onReset={onReset}>
<div>
<label>
姓名
<input
value={formData.name}
onChange={e =>
dispatch({ type: "patch", formData: { name: e.target.value } })
}
/>
</label>
</div>
<div>
<label>
年龄
<input
value={formData.age}
onChange={e =>
dispatch({ type: "patch", formData: { age: e.target.value } })
}
/>
</label>
</div>
<div>
<label>
民族
<input
value={formData.nationality}
onChange={e =>
dispatch({
type: "patch",
formData: { nationality: e.target.value }
})
}
/>
</label>
</div>
<div>
<button type="submit">提交</button>
<button type="reset">重置</button>
</div>
<hr />
{JSON.stringify(formData)}
</form>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
useMemo用来优化渲染
当项目中存在多个组件,某个组件的参数变化,而其他组件的参数并没有变化时,组件全部重新渲染会占用资源,显然不合理。如果只更新数据变化的组件就好了。此时就可以用到useMemo。用法示例:
const onClickChild = useMemo(() => {
const fn = div => {
console.log("on click child, m: " + m);
};
return fn;
}, [m]);
useMemo(()=>{fn},[n]),第一个参数是需要保持不变的函数,第二个参数是依赖。只有当依赖变化时,才会重新执行fn。与Vue的computed类似。
useCallback是useMemo的语法糖
两者功能完全一致。
useRef维持一个不变的n
如果需要一个值,在组件不断render时该变量保持不变(注意。不是指值不变,而是指地址),可以用useERef进行初始化达到这个目标。
import React from "react";
import ReactDOM from "react-dom";
const rootElement = document.getElementById("root");
function App() {
const nRef = React.useRef(0);
const log = () => setTimeout(() => console.log(`n: ${nRef.current}`), 1000);
return (
<div className="App">
<p>{nRef.current} 这里并不能实时更新</p>
<p>
<button onClick={() => (nRef.current += 1)}>+1</button>
<button onClick={log}>log</button>
</p>
</div>
);
}
ReactDOM.render(<App />, rootElement);
原本根据React函数式编程的思想,每次render之后,会产生新的n。但使用useRef之后,n始终是同一个n,不会随着render而产生新的n来代替原来的n。n所代表的值存在nRef.current内。
useContext相当于加强版的useRef
useRef是在组件内保持一个变量不变,而useContext能够在一个范围内保持一个变量不变。用法:
const themeContext = React.createContext(null);
function App() {
const [theme, setTheme] = React.useState("red");
return (
<themeContext.Provider value={{ theme, setTheme }}>
<div className={`App ${theme}`}>
<p>{theme}</p>
<div>
<ChildA />
</div>
<div>
<ChildB />
</div>
</div>
</themeContext.Provider>
);
}
function ChildA() {
const { setTheme } = React.useContext(themeContext);
return (
<div>
<button onClick={() => setTheme("red")}>red</button>
</div>
);
}
function ChildB() {
const { setTheme } = React.useContext(themeContext);
return (
<div>
<button onClick={() => setTheme("blue")}>blue</button>
</div>
);
}
此时,在themeContext.Provider标签内,useContext在A和B插件内都能生效。
自定义hooks
Hooks的使用更加简洁,减少了class组件中的this指向不明确的情况。每调用一次hooks,就会生成一份独立的状态。但是在某些情况下,还是需要自己定义hooks来实现一些功能。
如何构建一个hook,在第一次不更新,后续都更新的componentDidUpdate呢?
import {useEffect, useRef} from "react";
export const useUpdate=(fn:()=>void,deps:any[])=>{
const count = useRef(0)
useEffect(()=>{
count.current+=1
})
useEffect(()=>{
if(count.current>1){
fn()
}
},deps) }
此时在其他位置导入 useUpadate并使用,就能达到第一次不更新,后续都更新的componentDidUpdate的效果。