React三大基础Hook使用
# 前言
上一次写React还是class组件横行的年代,但是由于一直没有合适的项目做所以也丢的差不多了。这次正好从hook开始重学一遍React,从本文开始此React系列将是重学React的笔记,做一个记录。在16.8以后React退出了hooks的写法,可以让你在函数组件中使用类组件的state以及其他的react特性。也就是说全面拥抱函数式编程了,class组件已然没落了。下面就开始学习一下React官方提供的几个基础hooks吧
# useState
useState
是React
官方提供的一个可以在函数中使用state
的hook
。他和vue3
的响应式api
非常像,在响应式api
出现前,data
被绑定在了组件的配置项
里,导致开发过程非常死板不灵活,而有了响应式api
则可以随处定义响应式变量
,代码复用逻辑复用变得更加灵活。useState
也是起到相同的作用,在他出现前,函数式组件只能作为一个无状态
组件,有状态的都必须用class组件
去实现,有了他我们也可以随处抽离逻辑了。
useState的语法如下
const [state, setState] = useState(initialState);
useState函数接收一个默认值,返回一个数组,数组第一项是状态自身,第二项是修改这个值的函数,所以一般使用都是将这两项解构出来。
这个api比较简单就不做更多的演示了,直接看下一个
# useEffect
useEffect是一个非常有意思的hooks,他会根据传入的参数个数和传参类型不同起到不同的作用。
useEffect(() => {
console.log('useEffect被执行')
});
2
3
上面是一个最简单的例子,而我们传入的回调函数会在每次dom更新之后被执行。可以这时候会觉得他和componentDidUpdate
非常像了,其实不是的,useEffect
会在浏览器完成布局与绘制之后
,在一个延迟事件中被调用。
这使得他可以用来做很多副作用函数,而不会阻塞浏览器的渲染。
如果想要让useEffect只执行一次,并且销毁组件后还可以执行一次回调可以这样写
useEffect(() => {
console.log('useEffect被执行')
return ()=>{
console.log('useEffect跟着组件一起销毁')
}
});
2
3
4
5
6
这样useEffect会在组件渲染之后执行一次,并且在组件销毁前执行一次return的函数,这样做是为了防止内存泄露。
怎么样是不是很有意思,感觉他结合了class组件的好几个生命周期。
接下来看看传入两个参数有什么作用
useEffect(() => {
console.log('useEffect被执行')
},[]);
2
3
上面例子说了如果只传入一个函数,那useEffect会被执行多次,而如果我们只想要他执行一次就可以传入第二个参数,一个空数组。至于为什么是空数组,看完下面的例子就明白了
下面介绍他最大的作用,监听
是的没错,就是和vue的watch一样
const [id,setId] = useState('')
useEffect(() => {
console.log('useEffect被执行')
},[id]);
2
3
4
在第二个参数中传入一个非空数组,这样一来 只要id发生了变化useEffect就会被再次执行。就相当于监听id的变化一样。并且不只是传入一个id,[]里面可以传入多个参数,而只要这些参数里其中一个发生了变化都会触发 useEffect的再次执行。(注意:如果依赖的是一个引用类型,那react会以内存地址为依据去比对,也就是说只要内存地址没变,即使这个变量的属性变了也并不会触发useEffect的执行。所以想要监听引用类型的话最好是去监听他的某个属性)
下面在补充一下一种情况
useEffect(() => {
console.log('useEffect被执行')
return ()=>{
console.log('useEffect跟着组件一起销毁')
}
},[id]);
2
3
4
5
6
如果第一个参数有返回一个销毁函数的话,那每次依赖项发生变化都会销毁上一个useEffect并且重新创建一个useEffect再执行。
# useContext
组件通信一直都是组件化开发的一个必不可少的问题,如何解决复杂场景的组件通信也是每个coder必须掌握的技巧。在react里组件传值也可以直接通过props传递,和vue一样,但是组件的隔代通信就和vue有点不同了。
回顾一下在vue中,组件的隔代通信除了vuex
之外还有Eventbus
,provide inject
,$attr
等。那在react中当然也是有提供组件隔代传值的方法的,比如传递上下文
先看看react官方对context的描述
Context 提供了一个无需为每层组件手动添加 props,就能在组件树间进行数据传递的方法。在一个典型的 React 应用中,数据是通过 props 属性自上而下(由父及子)进行传递的,但这种做法对于某些类型的属性而言是极其繁琐的(例如:地区偏好,UI 主题),这些属性是应用程序中许多组件都需要的。Context 提供了一种在组件之间共享此类值的方式,而不必显式地通过组件树的逐层传递 props。
个人觉得其实和eventbus非常相似,也是提供一个组件内全局的state,谁需要谁引入。无需父组件一层一层传递props。
# React.createContext
React.createContext
可以创建一个局部的全局上下文,他提供的Provider
和Consumer
两个组件,分别作用是Provider
用来包裹子组件也就是确定这个上下文的作用域,被包裹的子组件可以引入父组件暴露的这个context
,并通过Consumer
来消费这个context
。下面一起来看看语法
父组件
import React, { createContext } from "react";
import GeneralC from "./GeneralC";
import FnC from "./FnC";
import ClassC from "./ClassC";
export const MyContext = createContext('') //此处创建context,可以传入默认值
export default function App() {
return (
//Provider组件接收一个value属性,这个value就是传递给子组件的props
<MyContext.Provider value={{ name: `context's value is string!` }}>
{/*这里写后面要进行包裹的子组件,此处先行导入后续需要消费context的3个组件*/}
<GeneralC/>
<hr/>
<FnC/>
<hr/>
<ClassC/>
</MyContext.Provider>
);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
子组件
import React, { useReducer } from "react";
import MyContext from "./main"; //从父组件里引入context
const GeneralC = () => {
return (
//使用Consumer包裹之后,内部可以接收到Provider的value
<MyContext.Consumer>
{(value) => {
return (
<div>
第一种使用Context方式获取的值:{JSON.stringify(value)}
</div>
);
}}
</MyContext.Consumer>
);
};
export default GeneralC;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
如果创建了多个上下文也是可以的,只需要一层层包裹就行了,在消费context
的时候也层层包裹即可
//这里就创建了两个上下文 分别是UserContext和GoodsContext
<UserContext.Provider value={{user,setUser}}>
<GoodsContext.Provider value={'goods'}>
<Item1/>
<div className={styles.box}>
父组件的state,
<p>{user.name}</p>
<p>{user.age}</p>
</div>
<Item2/>
</GoodsContext.Provider>
</UserContext.Provider>
2
3
4
5
6
7
8
9
10
11
12
<UserContext.Consumer>
{(value) => (
<GoodsContext.Consumer>
{
(goods)=>(
<div>
第一种使用Context方式获取的值:{JSON.stringify(value)}
嵌套的Consumer{goods}
</div>
)
}
</GoodsContext.Consumer>
)
}
</UserContext.Consumer>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
这样的写法显然并不方便,这个时候就轮到主角useContext
登场了
useContext主要解决子组件在消费context时的繁琐操作
前面创建上下文传递value等操作还是不变,在消费上下文的时候不再需要使用Consumer,而是通过useContext来获取value。
import React, { useReducer,useContext } from "react";
import MyContext from "./main"; //从父组件里引入context
const value = useContext(MyContext) //在这里就可以获取到value
const GeneralC = () => {
return (
<div>
第一种使用Context方式获取的值:{JSON.stringify(value)}
</div>
);
};
export default GeneralC;
2
3
4
5
6
7
8
9
10
11
12
13
这样即使有多个context,也只需要分别从useContext里取出来就行了。非常的方便
注意
所有调用了useContext的子组件都会在context发生变化时重新渲染,所以更新开销大的子组件可以使用memoization (opens new window) 来优化。