React Hooks 介绍
React是主流的前端框架,v16.8版本引入了全新的的API,叫做React Hooks。React的核心是组件,在v16.8版本之前,组件的标准写法是类(class)。下面是一个简单的类组件:
import React from "react";
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
n: 1,
};
}
onClick = () => {
this.setState((state) => ({
n: state.n + 1,
}));
};
render() {
return (
<div>
{this.state.n}
<button onClick={this.onClick}>+1-1</button>
</div>
);
}
}这个组件只是实现一个简单的+1操作,代码已经很多了,真实的App有多个类按照层级,一层一层构成,复杂度成倍增加。所以React团队希望,组件不要变成复杂的容器,组件的最佳写法应该是函数,而不是类。
但是函数组件没有state,也没有生命周期,所以React Hooks 的设计目的,就是加强版函数组件,完全不使用"类",就能写出一个全功能的组件。
Hook 这个单词的意思是"钩子"。
React Hooks 的意思是,组件尽量写成纯函数,如果需要外部功能和副作用,就用钩子把外部代码"钩"进来。 React Hooks 就是那些钩子。
它包括:
useState
使用状态
const [n,setN] = React.useState(0)
const [user,setUser] = React.useState({name:"leehome"})注意事项
- 如果state是一个对象,不能部分setState,因为setState不会帮我们合并属性
- setState(obj)如果obj地址不变,那么React就认为数据没有变化
import React, { useState } from "react";
function App() {
const [user, setUser] = useState({ name: "joe", age: 18 });
const onClick = () => {
setUser({ name: "leehome" });
};
return (
<div>
<span>{user.name}</span>
<hr />
<span>{user.age}</span>
<hr/>
<button onClick={onClick}>Click!</button>
</div>
);
}
tips
setState优先使用函数形式:setN(i=>i+1)
useReducer
使用useReducer分4步走:
- 创建初始值initialStateaction
- 创建所有操作reducer(state,action)
- 传给useReducer,得到读和写API
- 调用写({type:'操作类型'})
总的来说,useReducer是useState的复杂版,代码如下:
import React, { useReducer } from "react";
import ReactDOM from "react-dom";
const initial = {
n: 0
};
const reducer = (state, action) => {
switch(action.type){
case("add"):
{
return { n: state.n + action.number }
}
case("multi"): {
return { n: state.n * 2 };
} default:{
throw new Error("unknown type");
}
}
};
function App() {
const [state, dispatch] = useReducer(reducer, initial);
const { n } = state;
const onClick = () => {
dispatch({ type: "add", number: 1 });
};
const onClick2 = () => {
dispatch({ type: "add", number: 2 });
};
return (
<div className="App">
<h1>n: {n}</h1>
<button onClick={onClick}>+1</button>
<button onClick={onClick2}>+2</button>
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
useContext
上下文
全局变量是全局的上下文
上下文是局部的全局变量
使用方法
- 使用C=createContext(initial)创建上下文
- 使用<C.provider>赋值圈定作用域
- 在作用域内使用useContext(C)来使用上下文
代码示例:
import React, { createContext, useState, useContext } from "react";
import ReactDOM from "react-dom";
import "./styles.css";
const C = createContext(null);
function App() {
console.log("App 执行了");
const [n, setN] = useState(0);
return (
<C.Provider value={{ n, setN }}>
<div className="App">
<Baba />
</div>
</C.Provider>
);
}
function Baba() {
const { n, setN } = useContext(C);
return (
<div>
我是爸爸 n: {n} <Child />
</div>
);
}
function Child() {
const { n, setN } = useContext(C);
const onClick = () => {
setN(i => i + 1);
};
return (
<div>
我是儿子 我得到的 n: {n}
<button onClick={onClick}>+1</button>
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
useEffect
useEffect(副作用)对环境的改变即为副作用,如修改document.title
为了更好的理解,我们可以把他看作成afterRender,在每次rener后执行
用途
作为componentDidMount使用,[ ]作用第二个参数
作为componentDidUpdate使用,可指定依赖
作为componentWillUnmount使用,通过return
代码示例:
import React, { createContext, useState, useContext } from "react";
import ReactDOM from "react-dom";
function App() {
const [n, setN] = useState(0);
const onClick=()=>{
setN(i=>i+1)
}
useEffect(()=>{
console.log('第一次渲染')//第一次渲染执行
},[])
useEffect(()=>{
console.log('第一次渲染')/n变化时执行
},[n]) return (
<div>
n:{n}
<button onClick={onClick}>+1</button>
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);useLayoutEffect
useLayoutEffect(布局副作用)在浏览器渲染之前执行
useMemo
要理解React.useMemo,那得先讲下React.Memo,React默认有多余的render,比如:
import React from "react";
import ReactDOM from "react-dom";
function App() {
const [n, setN] = React.useState(0);
const [m, setM] = React.useState(0);
const onClick = () => {
setN(n + 1);
};
return (
<div className="App">
<div>
<button onClick={onClick}>update n {n}</button>
</div>
<Child data={m}/>
</div>
);
}
function Child(props) {
console.log("child 执行了");
console.log('假设这里有大量代码')
return <div>child: {props.data}</div>;
}
const Child2 = React.memo(Child);
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);我们发现点击改变n的时候,m依赖的函数组件<Child>也随之执行了
const Child2 = React.memo(Child);但是我们把<Child>改为<Child2>的时候,就不会出现上面的情况,就避免了多余的render
但是React.Memo有一个bug,添加了监听函数,马上就破功了,
const onClickChild = () => {
console.log(m);
};
return (
<div className="App">
<div>
<button onClick={onClick}>update n {n}</button>
</div>
<Child2 data={m} onClick={onClickChild} />
{/* Child2 居然又执行了 */}
</div>
);
}
function Child(props) {
console.log("child 执行了");
console.log("假设这里有大量代码");
return <div onClick={props.onClick}>child: {props.data}</div>;
}因为App运行会在执行函数的时候,生成新的函数,新旧函数虽然功能一样,但是地址不一样。所以我们可以用React.useMemo
const onClickChild = useMemo(() => {
}, [m])这个时候只要依赖值m不变,函数组件Child就不会后执行。
注意:
useMemo第一个参数是()=>value,第二个参数是依赖[m],如果value是一个函数,就得写成: ()=>(x)=>{console.log(x)}
是不是很难用?于是就有了React.useCallback
useCallback(x => log(x), [m])等价于 useMemo(() => x => log(x), [m])useRef
常见的useRef是引用一个标签元素,这里我就不介绍了
如果你需要一个值,在组件渲染的时候保持不变,你可以用useRef
比如:
初始化:const count =useRef(0)//注意括号里面是{current:0}的缩写,他引用的是个对象
读取:count.current
你还可以用useRef引用一个DOM对象
import React, { useRef } from "react";
import ReactDOM from "react-dom";
import "./styles.css";
function App() {
const buttonRef = useRef();
return (
<div className="App">
<Button2 ref={buttonRef}></Button2>
{/* 看浏览器控制台的报错 */}
</div>
);
}
const Button2 = props => {
return <button className="red" {...props} />;
};
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);我们想引用一个函数组件,(Class组件不报错可直接引用)系统报错:
这个时候我们就要用到forwardRef
import React, { useRef } from "react";
import ReactDOM from "react-dom";
import "./styles.css";
function App() {
const buttonRef = useRef();
console.log(buttonRef)
return (
<div className="App">
<Button3 ref={buttonRef}>按钮</Button3>
</div>
);
}
const Button3 = React.forwardRef((props, ref) => {
return <button className="red" ref={ref} {...props} />;
});
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);控制台打出:
