传送门:
React教程(一):React基础
React教程(二):React组件基础
React教程(三):React组件通信
React教程(四):React组件进阶
hooks本质:一套能够使函数组件更强大更灵活的
钩子
React 体系里组件分为类组件和函数组件
经过多年的实战,函数组件是一个更加匹配React设计理念UI=f(data)
,也更有利于逻辑拆分与重用的组件表达形式,而先前的函数组件是不可以有自己的状态的,为了能让函数组件可以拥有自己的状态,所以React从v16.8开始,Hooks应允而生。
注意点:
Hooks的出现解决了俩个问题
1.组件的状态逻辑复用 2.class组件自身的问题
作用: useState为函数组件提供状态(state)
使用步骤:
useState
函数useState
函数,并传入状态的初始值useState
函数的返回值中,拿到状态和修改状态的方法代码实现:
// 引入react核心包,导入useState
import React, { useState } from "react";
function App() {
// 调用useState函数并传入state初始值,从该函数返回值中拿到状态money和修改状态的方法setMoney
const [money, setMoney] = useState(1000);
const addMoney = () => {
setMoney(money + 1000);
};
return (
<div>
icy的钱:{money}
<button onClick={addMoney}>点击让icy的钱变多</button>
</div>
);
}
export default App;
读取状态:
该方法提供的状态,是函数内部的局部变量,可以在函数的任何位置使用。
修改状态:
最新的状态值
// 当调用setMoney的时候,组件内部代码会被执行一次!
// 首次渲染
// 首次被渲染的时候,组件内部的代码会被执行一次
// 其中useState也会执行一次
// 更新渲染 money会更新
// 1.app组件会再次渲染,这个函数会再次执行
// 2.useState再次执行,得新的money值不是初始值1000而是setMoney后的新值
function App() {
const [money, setMoney] = useState(1000);
const addMoney = () => {
setMoney(money + 1000);
};
return (
<div>
icy的钱:{money}
<button onClick={addMoney}>点击让icy的钱变多</button>
</div>
);
}
const [str,setStr] = useState('icy') // 以字符为初始值
const [list,setList] = useState([]) // 以数组为初始值
副作用是相对于主作用来说的,一个函数除了主作用就是副作用,对于React组件来说,主作用是根据(state/props)渲染UI,除此之外都是副作用(如手动修改dom)。
常见的副作用:
useEffect函数的作用就是为reac函数组件提供副作用处理的。
// 引入react核心包,1.导入useState 、useEffect
import React, { useState, useEffect } from "react";
function App() {
const [money, setMoney] = useState(1000);
const addMoney = () => {
setMoney(money + 1000);
};
// 2.定义副作用
useEffect(() => {
// 3.dom操作
document.title = `icy的${money}`;
});
return (
<div>
icy的钱:{money}
<button onClick={addMoney}>点击让icy的钱变多</button>
</div>
);
}
注意:该函数每次调用都会执行一次,当我们通过修改状态(setState)更新组件时,useEffect也会不断执行。
// 1.默认状态(无依赖)
// 组件初始化的时候执行一次,等到每次更新后会再次执行
useEffect(() => {
document.title = `icy的${money}`;
});
// 2.添加依赖项 空数组[]
// 只在首次渲染时执行一次,之后不再执行
useEffect(() => {
console.log("icy");
}, []);
// 3.添加特定依赖项
// 副作用函数在首次渲染时执行,在依赖项发生变化时才会再次执行。
useEffect(() => {
console.log("icy has much money");
}, [money]);
需求描述: 自定义一个hooks函数,实现获取滚动距离Y
如: const [y] = useWindowScroll()
import { useState } from "react";
export function useWindowScroll() {
const [y, setY] = useState(0);
window.onscroll = () => {
// 获取页面滚动距离
const top = document.documentElement.scrollTop || document.body.scrollTop;
setY(top);
};
return [y];
}
需求描述: 自定义一个hooks函数,当值发生变化可以自动同步到本地localStorage
import { useEffect, useState } from "react";
export function useStorage(key, defaultValue) {
const [value, setValue] = useState(defaultValue);
useEffect(() => {
localStorage.setItem(key, value);
}, [value]);
return [value, setValue];
}
还有很多自定义Hooks的非常好用的api可以使用
有一个叫ahooks的库:https://ahooks.js.org/
感兴趣的可以自行了解。
使用场景:
参数只会在组件的初始值渲染中起作用,后续渲染时会被忽略。如果初始state需要通过计算才能获得,则可以传入一个函数,在函数计算并返回初始的state,此函数只在初始渲染时被调用。
语法:
const [name,setName] = useState(()=>{
// 具体的计算逻辑
return xxxx
})
使用场景:
在组件被销毁时,如果有些副作用操作需要被清理,就可以使用此语法,比如常见的定时器。
场景1:
组件销毁后,定时器还在执行,这时候需要return一个函数用来清除定时器。
代码演示:
const Test = () => {
useEffect(() => {
let timer1 = setInterval(() => {
console.log("定时器执行");
}, 1000);
return () => {
console.log("清理副作用函数执行了");
// 编写清理副作用的代码
clearInterval(timer1);
};
}, []);
return <div>test</div>;
};
function App() {
const [isShow, setIsShow] = useState(true);
return (
<div className="wrapper">
{isShow ? <Test /> : null}
<button
onClick={() => {
setIsShow(!isShow);
}}
>
切换
</button>
</div>
);
}
使用场景:
如何在useEffect中发送网络请求,并且封装同步async await操作。
语法要求:
不可以直接在useEffect的回调函数外层直接包裹await,因为异步会导致清理函数无法直接返回
错误写法
:
useEffect(async () => {
const res = await fetch("http://geek.itheima.net/v1_0/channels")
.then((response) => response.json())
.then((data) => console.log(data));
console.log(res);
}
}, []);
正确写法
:
在内部单独定义一个函数,然后把这个函数使用async包装成同步
useEffect(() => {
async function fetchData() {
const res = await fetch("http://geek.itheima.net/v1_0/channels")
.then((response) => response.json())
.then((data) => console.log(data));
console.log(res);
}
fetchData();
}, []);
以前写过一篇博客,可以看看:快速理解useRef
使用场景:
1.在函数组件中获取真实dom
2.保存值
使用步骤:
1.导入useRef
2.执行useRef函数并传入null,返回值为一个对象,内部有一个current属性存放拿到的dom对象(组件实例)
3,通过ref绑定要获取的元素或组件
获取dom
// 引入react核心包,1.导入useState 、useEffect,useRef
import React, { useEffect, useRef } from "react";
function App() {
const h1Ref = useRef(null);
useEffect(() => {
// 组件渲染后才能拿到h1Ref,而副作用在组件渲染完成后才执行
console.log(h1Ref.current);
}, []);
return (
<div className="wrapper">
<h1 ref={h1Ref}>11111111111</h1>
</div>
);
}
获取组件实例:
函数组件由于没有实例,不能使用ref获取,如果想获取组件实现,必须是类组件。
class Test extends React.Component {
getName = () => {
return "my name is icy";
};
state = {
name: "icy",
};
render() {
return <div>111</div>;
}
}
function App() {
const testRef = useRef(null);
useEffect(() => {
console.log(testRef.current);
}, []);
return (
<div className="wrapper">
<Test ref={testRef} />
</div>
);
}
}
current 里拿到的是组件的实例对象,所以可以拿到组件内的方法,直接使用testRef. xxx点语法可以调用。
实现跨组件通信
使用步骤:
代码实现:
// 引入react核心包,1.导入useState 、useEffect,useRef
import React, { createContext, useContext, useEffect, useRef } from "react";
// 创建context对象
const Context = createContext();
function TestA() {
return <TestB />;
}
function TestB() {
return <TestC />;
}
function TestC() {
const name = useContext(Context);
return <div>my name is {name}</div>;
}
function App() {
const testRef = useRef(null);
useEffect(() => {
console.log(testRef.current);
}, []);
return (
// Provier包裹顶层组件
<Context.Provider value={"icy"}>
<div className="wrapper">
<TestA />
</div>
</Context.Provider>
);
}
export default App;