React之Hooks知识点
知识点
重要就6个Hooks:
- useState
- useEffect
- useContext
- useRef
- useCallback
- useMemo
useState
React 以前都是以类来实现,新版本实现是以函数来实现。React 现在重点以函数为主,函数是 React 的第一公民
- 利用函数不用在改变 this 指针
useState介绍
它的主要作用就是保存状态,他接受一个参数就是默认值,它返回一个数组,数组第一个就是数据,第二个就是改变数据的方法
改变数据的方法里面的参数是函数,函数必须有 return
import React, { useState } from "react";
const APP = () => {
const [name, setname] = useState("首页");
function changename() {
setname(() => {
let name = "首页";
return name + parseInt(10 * Math.random());
});
}
return (
<div>
<span>{name}</span>
<button onClick={changename}>点击修改</button>
</div>
);
};
export default APP;useState 注意的四个方面
组件会重新执行 避免死循环
import { useState } from "react";
function FunctionPage() {
const [count, setCount] = useState(0);
setCount(count + 1);
return (
<div>
<h1>FunctionPage</h1>
</div>
);
}
export default FunctionPage;注意
你这样做 代码会无限循环,因为只要数据更新,它就会重新执行,重新执行的时候又会执行 setCount(count + 1);所以会无限循环
异步执行
- 错误写法
import { useState } from "react";
function FunctionPage() {
const [count, setCount] = useState(0);
const handleClick = () => {
setCount(count + 1);
setCount(count + 1);
setCount(count + 1);
setCount(count + 1);
console.log("点击测试结果", count);
};
return (
<div>
<div>结果是{count}</div>
<button onClick={handleClick}>点击我</button>
</div>
);
}
export default FunctionPage;注意
你这样写的时候,你会发现 count 只会加 1,因为 setCount 是异步的,它会先执行完所有的 setCount,然后再执行 console.log("点击测试结果", count);所以 count 只会加 1
- 正确写法
import { useState } from "react";
function FunctionPage() {
const [count, setCount] = useState(0);
const handleClick = () => {
setCount((count) => count + 1);
setCount((count) => count + 1);
setCount((count) => count + 1);
setCount((count) => count + 1);
setCount((count) => {
console.log("结果是" + count);
return count;
});
};
return (
<div>
<div>结果是{count}</div>
<button onClick={handleClick}>点击我</button>
</div>
);
}
export default FunctionPage;注意
在开发模式和严格模式下你会发现 console 会执行了两次,这是因为 React 会在每次渲染后执行 useEffect,所以会执行两次,但是生产模式下就不会执行两次,要是想去掉直接在 main.jsx 里面去掉<StrictMode>就可以了
缓存
如果在短时间内大量多次频繁的渲染数据,它渲染数据仅仅会执行一次,因为它是缓存了
import { useState } from "react";
function FunctionPage() {
const [count, setCount] = useState(0);
console.log("组件渲染");
const handleClick = () => {
setCount((count) => count + 1);
setCount((count) => count + 1);
setCount((count) => count + 1);
setCount((count) => count + 1);
setCount((count) => {
console.log("结果是" + count);
return count;
});
};
return (
<div>
<div>结果是{count}</div>
<button
onClick={() => {
handleClick();
}}
>
点击我
</button>
</div>
);
}
export default FunctionPage;注意
- 你会发现
console.log("组件渲染")只执行了一次,因为它是缓存了
- 第二种不适用 useState 的话 它没有缓存了
import { useState } from "react";
function FunctionPage() {
const [count, setCount] = useState(0);
let x = 100;
const handleClick = () => {
setCount((count) => {
count += 1;
x += 1;
console.log(`count:${count}那么x:${x}`);
return count;
});
};
return (
<div>
<div>结果是{count}</div>
<button
onClick={() => {
handleClick();
}}
>
点击我
</button>
</div>
);
}
export default FunctionPage;警告
在这里面你会发现 x 永远是 101
函数内部使用
注意
你要是把 useState 现在函数外面,是不可以的. 它会直接报错
useEffect
useEffect 主要的作用就是替代生命周期函数
它接收两个参数,第一个参数表示回调函数,第二个表示生命周期函数
第二个参数表示它什么时候执行,如果没有第二个参数表示只要组件渲染都执行
重点第二个函数
- 当 useEffect 没有第二个参数
组件的初始化和更新都会执行
useEffect(() => {
console.log("哈哈哈");
});- 当 useEffect 第二个参数是空数组(仅仅执行一次)
初始化,和卸载的时候执行,相当于 componentDidMount,componentWillUnMount
useEffect(() => {
console.log("哈哈哈");
}, []);- 当有一个或者多个值的数组
useEffect(() => {
console.log("哈哈哈");
}, [name, age]);当有一个值的时候,该值有变化就执行
当有多个值的时候,只要有一个不相等就执行
监听函数
- 有 return 就表示卸载
useEffect(() => {
//监听
window.addEventListener("resize", onResize, false);
//监听完了就考虑卸载,卸载的时候必须是return
return () => {
window.removeEventListener("resize", onResize, false);
};
}, []);useContext
Context 主要是为了方便爷孙之间的传值,有的时候不希望一层层传递了,需要跨级传递
(1)创建并暴露出 Context
import React, { useState, createContext } from "react";
export const ResultContext = createContext();(2) 父组件
里面必须包含子组件
并且用 provider 包裹
import React, { useState, createContext } from "react";
import HeaderComponent from "./component/Header";
export const ResultContext = createContext();
const APP = () => {
const [age, setage] = useState(0);
function handleClick() {
setage(() => {
return age + 1;
});
}
return (
<div>
<div>{age}</div>
<button onClick={handleClick}>点击改变</button>
<ResultContext.Provider value={age}>
<HeaderComponent></HeaderComponent>
</ResultContext.Provider>
</div>
);
};
export default APP;(3) 子组件使用
- 函数 必须使用 useContext
import React, { useContext } from "react";
import { ResultContext } from "../App";
const HeaderComponent = () => {
const data = useContext(ResultContext);
return <div>子属性{data}</div>;
};
export default HeaderComponent;- 要是类 就必须用 Provider 和 Consumer
import React, { Component, Fragment } from "react";
import { PublicDataOne, PublicDataTwo } from "./App";
class ThirdWrapper extends Component {
constructor(props) {
super(props);
this.state = {
message: "第三层使用公共数据",
};
}
render() {
return (
<Fragment>
<div>
{this.state.message}
<PublicDataOne.Consumer>
{(value) => {
return (
<PublicDataTwo.Consumer>
{(valueTwo) => {
return (
<h1>
这就是第一个公共数据{value} 这就是第二个公共数据
{valueTwo}
</h1>
);
}}
</PublicDataTwo.Consumer>
);
}}
</PublicDataOne.Consumer>
</div>
</Fragment>
);
}
}
export default ThirdWrapper;useref
记忆功能(与 useState 区别)
获取 dom
组件 ref
暴露出组件方法
警告
useRef 不触发渲染
useRef 必须得用 current 来获取值
记忆功能
- 不触发渲染
举例子
- 这种情况下 它清除不了最开始的 timer,而是自己又新开了一个
import React, { useRef } from "react";
import Son from "./Son";
function App() {
let timer = null;
const handleClick = () => {
clearInterval(timer);
timer = setInterval(() => {
console.log("hello");
}, 1000);
};
return (
<div>
<h1 onClick={handleClick}>定时器</h1>
<Son></Son>
</div>
);
}
export default App;解决这个问题
- 使用 useRef 来解决
import React, { useRef } from "react";
import Son from "./Son";
function App() {
let timer = useRef(null);
const handleClick = () => {
clearInterval(timer.current);
timer.current = setInterval(() => {
console.log("hello");
}, 1000);
};
return (
<div>
<h1 onClick={handleClick}>定时器</h1>
<Son></Son>
</div>
);
}
export default App;获取 dom
- 获取 dom 改变文字颜色
import React, { useRef } from "react";
import Son from "./Son";
function App() {
const h1dom = useRef(null);
const handleClick = () => {
h1dom.current.style.color = "red";
};
return (
<div>
<h1 onClick={handleClick} ref={h1dom}>
定时器
</h1>
<Son></Son>
</div>
);
}
export default App;组件 ref
给组件绑定 ref
forwardRef 包裹组件
通过 ref 指定元素
父元素
import React, { useRef } from "react";
import Son from "./Son";
function App() {
const h1dom = useRef(null);
const handleClick = () => {
console.log(h1dom.current); // 就能获取到 <h1>son</h1
};
return (
<div>
<h1 onClick={handleClick}>获取dom</h1>
<Son ref={h1dom}></Son>
</div>
);
}
export default App;子元素
- Son 括号里面不用
{}直接就是 props,ref
- Son 括号里面不用
- forwardRef 包裹组件
- 通过 ref 指定元素
import React, { forwardRef } from "react";
function Son(props, ref) {
console.log("子元素渲染");
return (
<div>
<h1 ref={ref}> Son</h1>
</div>
);
}
export default forwardRef(Son);暴露出组件的方法
Son 还是得括号里写 props,ref
useImperativeHandle 指定 ref 暴露的方法或者属性,必须 return
通过 ref 暴露出我想指定的方法
子元素
import React, { forwardRef, useImperativeHandle } from "react";
function Son(props, ref) {
// 指定暴露的方法
useImperativeHandle(ref, () => {
return {
fn1() {
console.log("子组件暴露的方法1");
},
fn2() {
console.log("子组件暴露的方法2");
},
};
});
return (
<div>
<h1> Son</h1>
</div>
);
}
export default forwardRef(Son);父元素
import React, { useRef } from "react";
import Son from "./Son";
function App() {
const h1dom = useRef(null);
const handleClick = () => {
console.log(h1dom.current);
h1dom.current.fn1();
h1dom.current.fn2();
};
return (
<div>
<h1 onClick={handleClick}>获取dom</h1>
<Son ref={h1dom}></Son>
</div>
);
}
export default App;缓存
- memo
- usememo
- useCallback
memo
主要就是应用于 HTML
memo 是一个高阶组件,用于对组件进行包裹,从而对组件进行优化,减少组件的渲染次数
适用场景
当父元素变化 而子元素不需要变化的时候,用它包裹住组件,减少渲染
父元素
import { useCallback, useReducer, useState } from "react";
import Son from "./Son";
function App() {
const [count, setCount] = useState(0);
console.log("父元素渲染");
const handleClick = () => {
setCount(count + 1);
};
return (
<div>
<h1 onClick={handleClick}>父元素--{count}</h1>
<Son />
</div>
);
}
export default App;子元素
function Son() {
console.log("子元素渲染");
return (
<div>
<h1> Son</h1>
</div>
);
}
export default Son;这个时候你会发现只要 count 改变 父子组件一起渲染,浪费性能
改变子元素
- 这样通过包裹层,当父元素改变的时候,子元素不会重新渲染
import React, { memo } from "react";
function Son() {
console.log("子元素渲染");
return (
<div>
<h1> Son</h1>
</div>
);
}
export default memo(Son);useCallback
- 适用于缓存方法
适用场景
memo 有个致命的缺点 就是当父元素给子元素传方法的时候,子元素还是会重新渲染
这个时候就需要 useCallback 来包裹方法,从而减少子元素的渲染,
两个参数 第一个缓存的方法,第二个是依赖项 要是不写则每次都会重新生成一个方法
父元素
import { useCallback, useReducer, useState } from "react";
import Son from "./Son";
function App() {
const [count, setCount] = useState(0);
console.log("父元素渲染");
const handleClick = () => {
setCount(count + 1);
};
const fn = () => {};
return (
<div>
<h1 onClick={handleClick}>父元素--{count}</h1>
<Son fn1={fn} />
</div>
);
}
export default App;子元素
import React, { memo } from "react";
function Son() {
console.log("子元素渲染");
return (
<div>
<h1> Son</h1>
</div>
);
}
export default memo(Son);注意
- 这种情况 缓存失效了,因为每次父元素渲染的时候,都会重新生成一个 fn,所以子元素还是会重新渲染
改变父元素
import { useCallback, useReducer, useState } from "react";
import Son from "./Son";
function App() {
const [count, setCount] = useState(0);
console.log("父元素渲染");
const handleClick = () => {
setCount(count + 1);
};
// 缓存,依赖项必须写,不然每次都会重新生成一个方法
const fn = useCallback(() => {}, []);
return (
<div>
<h1 onClick={handleClick}>父元素--{count}</h1>
<Son fn1={fn} />
</div>
);
}
export default App;usememo
- 适用于属性,也可用于方法
适用场景
memo 有个致命的缺点 就是当父元素给子元素传属性的时候,子元素还是会重新渲染
这个时候就需要 useMemo 来包裹属性,从而减少子元素的渲染,
两个参数 第一个缓存的方法,第二个是依赖项 要是不写则每次都会重新生成一个方法
父元素
import { useCallback, useReducer, useState, useMemo } from "react";
import Son from "./Son";
function App() {
const [count, setCount] = useState(0);
console.log("父元素渲染");
const handleClick = () => {
setCount(count + 1);
};
const arr = [1, 2, 3, 4, 5];
return (
<div>
<h1 onClick={handleClick}>父元素--{count}</h1>
<Son arr={arr} />
</div>
);
}
export default App;子元素
import React, { memo } from "react";
function Son() {
console.log("子元素渲染");
return (
<div>
<h1> Son</h1>
</div>
);
}
export default memo(Son);注意
- 这种情况 缓存失效了,因为每次父元素渲染的时候,都会重新生成一个 arr,所以子元素还是会重新渲染
改变父元素
import { useCallback, useReducer, useState, useMemo } from "react";
import Son from "./Son";
function App() {
const [count, setCount] = useState(0);
console.log("父元素渲染");
const handleClick = () => {
setCount(count + 1);
};
const arr = useMemo(() => {
return [1, 2, 3, 4, 5];
}, []);
return (
<div>
<h1 onClick={handleClick}>父元素--{count}</h1>
<Son arr={arr} />
</div>
);
}
export default App;- 适用于方法
import { useCallback, useReducer, useState, useMemo } from "react";
import Son from "./Son";
function App() {
const [count, setCount] = useState(0);
console.log("父元素渲染");
const handleClick = () => {
setCount(count + 1);
};
const arr = useMemo(() => {
return [1, 2, 3, 4, 5];
}, []);
const func1 = useMemo(() => {
return function () {
console.log("func1");
};
}, []);
return (
<div>
<h1 onClick={handleClick}>父元素--{count}</h1>
<Son arr={arr} func1={func1} />
</div>
);
}
export default App;