hooks 钩子函数
- 用于解决 函数式组件没有办法定义 state 和 生命周期的问题。
- hooks 主要用于解决 类组件进行逻辑复用极其麻烦的问题
- hooks 本质就是函数
- 所有的hooks命名规定以
use
开头
useState 定义状态
let [状态,修改该状态的方法] = useState(初始值);
- 在同一个组件中可以使用 useState 定义多个状态
- 注意 useState 返回的 setState 方法,不会进行对象合并,需要自己将原先属性再传递一次
...data
- 注意 useState 返回的 setState 方法同样是异步方法
function App(props) {
// let [count, setCount] = useState(0)
let [data, setData] = useState({
name: "小明",
count: 0
});
let { count, name } = data;
return <div>
<p>{count}</p>
<button onClick={() => {
setData({
...data,
count: ++count
});
}}>递增</button>
<br />
<input
type="text"
value={name}
onChange={({ target }) => {
setData({
...data,
name: target.value
});
console.log(name);
}}
/>
<p>{name}</p>
</div>;
}
useEffect 副作用函数
生命周期函数:在生命周期函数中通常处理:将数据进行请求或者获取原生dom进行操作
- 不写依赖参数,该useEffect,会在组件挂载完及更新完之后都会执行
- 如果依赖参数为空数组,该useEffect,只会在挂载完之后执行,并在组件即将卸载时,执行该useEffect的返回函数
- 有依赖参数时,该useEffect,会在挂载完之后执行,以及依赖参数有变化时执行
useEffect(() => {
副作用要处理的事情
return () => {
副作用的回调函数
}
}, [依赖参数])
依赖参数
不写依赖参数时,该effect会在组件生命周期挂载完以及更新完之后都会执行
import React, { useState, useEffect } from "react";
function App(props) {
let [data, setData] = useState({
name: "小明",
count: 0
});
useEffect(() => {
console.log("挂载完成或更新完成");
});
let { count, name } = data;
return <div>
<p>{count}</p>
<button onClick={() => {
setData({
...data,
count: ++count
});
}}>递增</button>
</div>;
}
export default App;
传入依赖参数为空数组时,只会在挂载完成之后执行
import React, { useState, useEffect } from "react";
function App(props) {
let [data, setData] = useState({
name: "小明",
count: 0
});
useEffect(() => {
console.log("挂载完成或更新完成");
});
useEffect(() => {
console.log("挂载完成之后执行");
}, []);
// ...
}
export default App;
只在更新完成之后执行的方法
let isMount = false;
function App(props) {
// ...
useEffect(() => {
if (isMount) {
console.log("更新完成之后执行");
}
isMount = true;
});
// ...
</div>;
}
有依赖参数时,该useEffect会在挂载完成后以及依赖参数有变化时执行
useEffect(() => {
console.log("count改变了");
}, [count]);
副作用函数中的回调函数
useEffect(() => {
console.log("副作用函数");
return () => {
console.log("回调函数");
};
});
- 挂载阶段:组件挂载—副作用函数
- 更新阶段:
- 组件更新生成虚拟DOM
- 回调函数
- 组件更新完成
- 副作用函数
传入依赖参数为空数组时,会在挂载完成之后执行副作用函数,并且会在组件即将卸载时执行副总用函数中的回调函数
useEffect(() => {
console.log("副作用函数:组件挂载或更新完成");
return () => {
console.log("返还函数:即将卸载");
};
},[]);
所以返还函数用的最多的地方就是检测组件即将卸载
对应react的生命周期
不写参数时,对应挂载后和组件更新后
componentDidMount(){
console.log("组件挂载完毕");
}
componentDidUpdate(){
console.log("组件更新完毕");
}
写空数组时,只对应挂载后
componentDidMount(){
console.log("组件挂载完毕");
}
数组中写参数时,对应挂载后以及依赖参数有改变时执行
Effect中有返回函数时,返回函数执行对应卸载前
componentWillUnmount(){
console.log("组件即将卸载");
}
useRef
- 用于关联原生DOM节点,
- 直接绑定,如果过DOM节点有修改,React会同步ref的值
- 使用ref来记录组件更新前的一些数据
function Child(props) {
let [data, setData] = useState({
name: "小明",
count: 0
});
let { count, name } = data;
let prevCount = useRef(count); // 状态更新了,ref并不会更新
let countP = useRef();
useEffect(() => {
console.log(count, countP.current, prevCount);
});
return (
<div>
<p ref={countP}>{count}</p>
<button onClick={() => {
setData({
...data,
count: ++count
});
}}>递增</button>
// ...
</div>
);
}
如果ref中存储的是值,则需要手动更新
useEffect(() => {
prevCount.current = count
console.log(count, countP.current, prevCount);
});
通过 useRef 存储值来判断挂载与更新
let { count, name } = data;
let prevCount = useRef(count);
let initCount = useRef(count);
let initName = useRef(name);
let countP = useRef();
useEffect(() => {
prevCount.current = count;
console.log(count, countP.current, prevCount);
}, [count]);
useEffect(() => {
if (initCount.current === count && initName.current === name) {
console.log("挂载完成");
} else {
console.log("更新完成");
}
}, [count, name]);
useMemo
- 写法类似于useEffect,功能类似于vue中的计算属性
- 监听依赖参数的改变,返回新的数据
- 依赖参数也可以限定条件,不满足条件就不会执行
let age2 = useMemo(()=>{
console.log(1);
return age < 18?"未成年":"成年人";
},[age < 18]);
useCallback
- 是useMemo的变形
- 当useMemo中需要一个返回函数,在外部按需加载时,逻辑可能就会很复杂
- useCallback就整合了这个返回函数
let age2 = useMemo(()=>{
return ()=>age<18?"未成年":"成年人";
},[age < 18]);
let age2 = useCallback(()=>age<18?"未成年":"成年人",[age < 18]);
return <div>年龄阶段:{age2()}</div>
useContext
类组件中的context
- createContext
- Provider
- Consumer
- contextType
在类组件中,createContext用于跨层传递信息
使用<Provider>
组件包裹父级,并传递数据,那么包裹下的所有层级都能拿到这个数据
import React, {Component,createContext, useContext} from 'react';
let {Provider,Consumer} = createContext();
class Context extends Component {
render(){
return <div>
<myContext.Provider value={{info :"今天天气不错"}}>
<Parent />
</myContext.Provider>
</div>
}
}
通过<Consumer>
包裹子级来让子级接收数据
class Child extends Component {
render(){
return (
<Consumer>
{(context)=>{
console.log(context);
return <strong>这是祖先传下来的宝贝:{context.info} </strong>
}}
</Consumer>
)
}
}
简易方写法,静态static contextType = myContext;
let myContext = createContext();
class Child extends Component {
static contextType = myContext;
render(){
console.log(this.context);
return (
<strong>这是祖先传下来的宝贝: {this.context.info}</strong>
)
}
}
函数式组件中的useContext
useContext(myContext)
let myContext = createContext();
function Child2(){
let {info, dispatch} = useContext(myContext);
return (
<div><strong>这是祖先传下来的宝贝: {info} </strong></div>
)
}
useReducer
自定义hooks
把重复使用的逻辑抽离封装成hooks
import React, { useState, useEffect, Fragment } from 'react';
// 将重复使用的逻辑抽离出来,定义成hooks
function useScrollY() {
console.log(window.scrollY)
let [scrollY, setScrollY] = useState(window.scrollY);
// 组件卸载时通过useEffect的返还函数清除挂载的全局事件
useEffect(() => {
return () => {
window.onscroll = null;
};
}, []);
window.onscroll = function () {
setScrollY(window.scrollY);
};
return [scrollY, (newScrollY) => {
window.scrollTo(0, newScrollY);
// setScrollY(newScrollY);
}];
}
function App() {
let [scrollY, setScrollY] = useScrollY();
return (
<Fragment>
<style>
{
`
div {
width: 200px;
height: 400px;
font: 100px/200px "宋体";
text-align: center;
border: 2px solid #000;
}
span {
position:absolute;
left: 0;
top: 200px;
width: 100px;
font: 16px/2 "宋体";
background: #999;
color: #fff;
}
`
}
</style>
<div>1</div>
<div>2</div>
<div>3</div>
<div>4</div>
<div>5</div>
<div>6</div>
<div>7</div>
<span
style={{
top: 200 + scrollY
}}
onClick={() => {
setScrollY(0);
}}
>{scrollY}</span>
</Fragment>
);
}
export default App;
hooks优势
- 简化组件逻辑
- 复用状态逻辑
- 无需使用类组件编写
Hook 使用规则
- 命名以use开头
- 只在 React 函数中调用 Hook
- React 函数组件中
- React Hook 中
- 只在最顶层使用 Hook , 比如不能在for循环里使用,不能包其他上下文