Hook是什么?官方文档写的晦涩难懂。Hook 是一些可以让你在函数组件里“钩入” React state 及生命周期等特性的函数。Hook是用于函数组件的,因为函数组件没有生命周期,也是无状态组件,所以我们不能和类组件一样用构造函数里面的state和setState。但是,我们可以用内置Hook来复用不同组件之间的状态逻辑。
先来说说第一个内置hook——State Hook。
1.State Hook
一句话概括:this.state的代替品。
代码如下:
import React,{useState} from 'react';
function HuckUseState(props) {
// 取常量是为了避免用户修改 函数名一般是常量名前面加个set
const [count,setCount] = useState(0)
return (
<div>
<button onClick={()=>setCount(count+1)}>+</button>
{count}
<button onClick={()=>setCount((prev)=>prev-1)}>-</button>
</div>
);
}
export default HuckUseState;
(在app.js中import引入即可查看效果,哈哈哈是HockUseState我写错成Huck)
我们通过在函数组件里调用它来给组件添加一些内部 state,useState 会返回一对值:当前状态和一个让你更新它的函数,你可以在事件处理函数中或其他一些地方调用这个函数。它类似 class 组件的 this.setState,但是它不会把新的 state 和旧的 state 进行合并。
useState 唯一的参数就是初始 state。在上面的例子中,我们的计数器是从零开始的,所以初始 state 就是 0。但useState里面除了放单个的值,还可以放对象:
const [student,setStudent] = useState({
name:'liu'
age:10
})
还可以放字符串:
const [fruit,setFruit] = useState('banana')
另一个例子,很相似,也是修改咱们学生的年龄,代码如下:
~~~~~1
import React,{useState} from 'react';
function HuckUseState2(props) {
const [student,setStudent] = useState({
name:'liu',
age:10
})
return (
<div>
<button onClick={()=>setStudent({...student,age:student.age+1})}>+</button>
{student.age}
<button onClick={()=>setStudent({...student,age:student.age-1})}>-</button>
</div>
);
}
export default HuckUseState2;
效果和上面一样~只是在setStudent这个函数中,因为useState中传递的是对象,所以我们得把student解构出来。
2.useEffect
一句话概括:componentDidMount
和 componentDidUpdate 的代替品。
可以看最做是class组件中的componentDidMount(挂载后)、componentDidUpdate(更新后)和componentWillUnmount(卸载前)几个生命周期的大集合。给函数组件增加了操作副作用的能力(网络请求、更新Dom、事件的监听)
useEffect接受两个参数,第一个参数是一个callback,可以用来做一些副作用比如异步请求,修改外部参数等操作。
在callback函数中return了一个函数,这个函数会在组件卸载前执行,需要清除上次订阅的内容可以在这里面写。它会在调用一个新的effect之前对前一个effect进行清理,所以effect的清除阶段在每次重新渲染时都会执行,而不是只在卸载组件时候执行一次。
当然要记住,在react中我们的副作用分为无需清除的和需要清除的。简单来说副作用就是你的函数除了返回你需要的东西之外,还另外进行了其他的操作。而纯函数只返回你需要的数据。
下面我们来看代码,这段代码基于上文进行修改,我们为计数器增加了一个小功能:将 document 的 title 设置为包含了点击次数的消息。
1)如果只有一个参数callback
很明显,如果这个时候没有return,你一直点击按钮出现的就是下图的效果:
return何时执行:
~~~~~2
import React, { useState, useEffect } from 'react';
export default function HookUseEffect() {
const [count, setCount] = useState(0);
useEffect(() => {
// 这是模板字符串 ``
console.log(`You clicked ${count} times`)
document.title = `You clicked ${count} times`;
// 副作用消失时候执行 不一定是卸载
return ()=>{
console.log('取消订阅')
}
});
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
(未点击时效果)
(点击一次效果)
可以看出加了return后,之后每次点击【Click me】时候,先取消了订阅再出现【You clicked X times】,return是副作用消失时候执行(不一定是卸载)。
对比一下,使用 class 的示例:
在 React 的 class 组件中,render 函数是不应该有任何副作用的。一般来说,在这里执行操作太早了,我们基本上都希望在 React 更新 DOM 之后才执行我们的操作。
这就是为什么在 React class 中,我们把副作用操作到 componentDidMount 和 componentDidUpdate 函数中。但是如果于要写这也太麻烦了!
class Example extends React.Component {
constructor(props) {
super(props);
this.state = {
count: 0
};
}
componentDidMount() {
document.title = `You clicked ${this.state.count} times`;
}
componentDidUpdate() {
document.title = `You clicked ${this.state.count} times`;
}
render() {
return (
<div>
<p>You clicked {this.state.count} times</p>
<button onClick={() => this.setState({ count: this.state.count + 1 })}>
Click me
</button>
</div>
);
}
}
这是一个 React 计数器的 class 组件。它在 React 对 DOM 进行操作之后,立即更新了 document 的 title 属性。注意,在这个 class 中,我们需要在两个生命周期函数中编写重复的代码。(因为我们需要 组件在加载和更新时执行同样的操作)
2)第二个参数source是一个触发条件
是数组类型,如果数组中的值变化才会触发useEffect的callback回调。
1.没有传入该参数时,默认在每次render时都会重新执行回调。(如果有个return,那么就是第一次渲染:订阅,第二次:取消订阅,订阅)
2.传递一个[],只有在组件初始化或者销毁时才会触发。
3.传入[source],在依赖state发生改变时,才重新执行回调。
当传递一个[ ]时候是什么效果呢?
npm i react-router-dom@5.3)。
咱们写个简单的路由来看看效果,下图就是用路由来实现两个函数组件的切换。点击state和effect会切换到不同的组件。
~~~~~1和~~~~~2
注意!~~~~~2 中的代码要加参数 [ ]
App.js的代码为:
import './App.css';
import MyContext from './MyContext'
import Child from './Child';
import HookUseReducer from './HookUseReducer';
import myReducer from './StudentReducer'
import {
BrowserRouter as Router,
Route,
Link
} from 'react-router-dom'
import HookUseEffect from './HookUseEffect';
import HookUseState from './HookUseState';
function App() {
return (
<div className="App">
<Router>
<div>
<Link to='/state'>state</Link>
<br/>
<Link to='/effect'>effect</Link>
</div>
<Route path="/state" component={HookUseState}></Route>
<Route path='/effect' component={HookUseEffect}></Route>
</Router>
</div>
);
}
export default App;
1)我们先点state
2)再点effect
3)再切换到state
组件销毁时才调用
补充:避免在useEffect中做太多事情,如果有多个依赖可能发生变化时,会按照先后顺序执行。
而且也要避免死循环!(因为getDep每次都返回一个新对象,相当于[source]一直在更新)
还可以用社区的深度比较来解决。
3.useLayoutEffect
一句话概括,有闪烁情况时用这个hook。
和useEffect函数签名一致,是在Dom修改后同步触发,而且总是比useEffect先执行,会阻塞浏览器的渲染。
useEffect执行顺序:组件更新挂载完成=》浏览器dom绘制完成=》执行useEffect回调
useLayoutEffect执行顺序:组件更新挂载完成=》执行useLayoutEffect回调=》浏览器dom绘制完成
4.useContext
一句话概括,用于多层级的组件通信(传值)的。
直接说用法:
5.useReducer
一句话概括,用于状态管理。相当于简单的redux的使用。