你是否还在为复用有状态的组件发愁,或是一直在使用复杂的render Props和HOC,现在就让Hook来解决你的所有烦恼
一、使用类组件或者函数组件的弊端
1.1 虽然React一直提倡的是我们能够函数组件,但是当我们发现函数组件中需要状态时,我们可能不得不将其改变成类组件
1.2 在使用类组件时,我们不得不去通过箭头函数或者bind去绑定,而这又是比较消耗性能
1.3 复杂的生命周期
1.4 在复用组件时,难免要使用render Props或者HOC,这会使代码结构看起来极其复杂,HOC会让组件之间更多的嵌套
二、为什么要使用Hook
2.1 能优化前面提到的问题
2.2 在复用组件时,能使用自定义Hook,复用组件更加简单
三、useState
3.1 先让我们来对比一下hook和使用传统类的区别
import React, {useState, memo, useMemo, useCallback} from 'react';
export function HookTest() {
let [age, setAge] = useState(18);
let [name, setName] = useState("庄某某");
return (
<>
<div>
我叫{name}, 今天{age}岁
</div>
<button onClick={() => setAge(age + 1)}>加一岁</button>
<input type="text" onChange={(e) => setName(e.target.value)}/>
</>
)
}
import React, {Component} from 'react';
export class HookTest extends Component{
constructor(props){
super(props);
this.state = {
age: 18,
name: "庄子鹏"
}
}
setAge = () => {
this.setState({
age: this.state.age + 1
})
}
setName = (value) => {
this.setState({
name: value
})
}
render(){
const {age, name} = this.state;
return (
<>
<div>
我叫{name}, 今天{age}岁
</div>
<button onClick={() => this.setAge()}>加一岁</button>
<input type="text" onChange={(e) => this.setName(e.target.value)}/>
</>
)
}
}
通过这里的例子看好像并没有什么很独特的区别,取了语法上的不同
3.2 每次渲染都是一次独立的闭包
- 每一次渲染都有它自己的 Props 和 State
- 每一次渲染都有它自己的事件处理函数
- 当点击更新状态的时候,函数组件都会重新被调用,那么每次渲染都是独立的,取到的值不会受后面操作的影响
import React, {useState, memo, useMemo, useCallback} from 'react';
function HookTest(){
let [age,setAge] = useState(0);
function alertAge(){
setTimeout(()=>{
// alert 只能获取到点击按钮时的那个状态
alert(age);
},3000);
}
return (
<>
<p>{age}</p>
<button onClick={()=>setAge(age+1)}>+</button>
<button onClick={alertAge}>alertAge</button>
</>
)
}
//这里我们用传统类写法来实现以下
import React, {Component} from 'react';
export default class HookTest extends Component {
constructor(props) {
super(props);
this.state = {
age: 0
}
}
setAge = () => {
this.setState({
age: this.state.age + 1
})
}
alertAge = () => {
setTimeout(() => {
alert(this.state.age)
// alert 能获取到最新的那个state age });
}, 3000)
}
render() {
const {age} = this.state;
return (
<>
<p>{age}</p>
<button onClick={() => this.setAge()}>+</button>
<button onClick={() => this.alertAge()}>alertAge</button>
</>
)
}
}
这里我们也使用了传统的类写法去写,确实能够获得最新的状态
3.3 函数式更新
如果我们需要去通过上一次的最新的state去计算下一个state的值,可以将回调函数作为参数传给setState,该回调函数将接收先前的 state,并返回一个更新后的值。
import {useState, useEffect} from 'react';
export default function Counter2() {
let [age, setAge] = useState(0);
function lazyAdd() {
setTimeout(() => {
// alert 只能获取到点击按钮时的那个状态
setAge(age => age + 1);
}, 3000);
}
return (
<>
<p>{age}</p>
<button onClick={() => setAge(age + 1)}>+</button>
<button onClick={lazyAdd}>lazyAdd</button>
</>
)
}
//在这里我其实更想和上面的alert那个例子做比较,但是我发现好像没办法实现去alert最新的那个状态
3.4 这里我们还要讲一下Hook触发重新渲染的条件,Hook使用的是Object.is
import React, {useState, memo, useMemo, useCallback} from 'react';
export function HookTest() {
console.log('render');
let [age, setAge] = useState(18);
let [name, setName] = useState("庄某某");
return (
<>
<div>
我叫{name}, 今天{age}岁
</div>
<button onClick={() => setAge(18)}>加一岁</button>
//点击时并不会触发重新渲染,因为前后状态相同
<input type="text" onChange={(e) => setName(e.target.value)}/>
</>
)
}
import React, {Component} from 'react';
export class HookTest extends Component{
constructor(props){
super(props);
this.state = {
age: 18,
name: "庄子鹏"
}
}
setAge = () => {
this.setState({
age: 18
})
}
setName = (value) => {
this.setState({
name: value
})
}
render(){
console.log('render');
const {age, name} = this.state;
return (
<>
<div>
我叫{name}, 今天{age}岁
</div>
<button onClick={() => this.setAge()}>加一岁</button>
//点击时会触发重新渲染,即使前后状态相同
<input type="text" onChange={(e) => this.setName(e.target.value)}/>
</>
)
}
}
五、useEffect
5.1 useEffect是Hook中带来的一个新概念,叫做副作用,取代了传统class中的生命周期函数,Hook没有了所谓的生命周期函数
在这里我不是很理解副作用的含义,但我们可以把他理解成是组件渲染的一部分
import React, {useState, memo, useMemo, useCallback, useEffect} from 'react';
export function HookTest() {
let [age, setAge] = useState(18);
let [name, setName] = useState("庄某某");
useEffect(() => {
document.title = name;
})
return (
<>
<div>
我叫{name}, 今天{age}岁
</div>
<button onClick={() => setAge(age + 1)}>加一岁</button>
<input type="text" onChange={(e) => setName(e.target.value)}/>
</>
)
}
// 每次我setName之后,title都会改变
那每次触发重新渲染之后都会去执行useEffect里的内容,那如何去避免执行useEffect中的内容,这时候我们需要用到useEffect的第二个参数,依赖项
import React, {useState, memo, useMemo, useCallback, useEffect} from 'react';
export function HookTest() {
let [age, setAge] = useState(18);
let [name, setName] = useState("庄某某");
useEffect(() => {
document.title = age + name;
},[name])
return (
<>
<div>
我叫{name}, 今天{age}岁
</div>
<button onClick={() => setAge(age + 1)}>加一岁</button>
<input type="text" onChange={(e) => setName(e.target.value)}/>
</>
)
}
//只有当状态name发生改变且触发重新渲染时,useEffect里面的内容才会去执行
5.2 使用多个Effect
我们会在传统的react中使用多个生命周期,同样我们可以使用多个Effect来实现同样的功能,Effect的执行按照顺序来进行
import React, {useState, memo, useMemo, useCallback, useEffect} from 'react';
export function HookTest() {
let [age, setAge] = useState(18);
let [name, setName] = useState("庄某某");
useEffect(() => {
console.log(1)
})
useEffect(() => {
console.log(2)
}) return (
<>
<div>
我叫{name}, 今天{age}岁
</div>
<button onClick={() => setAge(age + 1)}>加一岁</button>
<input type="text" onChange={(e) => setName(e.target.value)}/>
</>
)
}
六、useReducer
6.1 在管理多个状态时可以使用useReducer
import React, {useReducer} from 'react';
const initialState = 0;
function reducer(state, action) {
switch (action.type) {
case 'increment':
return {age: state.age + 1};
case 'decrement':
return {age: state.age - 1};
default:
throw new Error();
}
}
function init(initialState){
return {age:initialState};
}
export function HookTest(){
const [state, dispatch] = useReducer(reducer, initialState,init);
return (
<>
Count: {state.age}
<button onClick={() => dispatch({type: 'increment'})}>+</button>
<button onClick={() => dispatch({type: 'decrement'})}>-</button>
</>
)
}
七、useContext
首先我们先来看一下传统react的context使用
//首先会有这么一个场景
class App extends React.Component {
render() {
return <Toolbar theme="dark" />;
}
}
function Toolbar(props) {
// Toolbar 组件接受一个额外的“theme”属性,然后传递给 ThemedButton 组件。
// 如果应用中每一个单独的按钮都需要知道 theme 的值,这会是件很麻烦的事,
// 因为必须将这个值层层传递所有组件。
return (
<div>
<ThemedButton theme={props.theme} />
</div>
);
}
class ThemedButton extends React.Component {
render() {
return <Button theme={this.props.theme} />;
}
}//当我们使用context
// Context 可以让我们无须明确地传遍每一个组件,就能将值深入传递进组件树。
// 为当前的 theme 创建一个 context(“light”为默认值)。
const ThemeContext = React.createContext('light');
class App extends React.Component {
render() {
// 使用一个 Provider 来将当前的 theme 传递给以下的组件树。
// 无论多深,任何组件都能读取这个值。
// 在这个例子中,我们将 “dark” 作为当前的值传递下去。
return (
<ThemeContext.Provider value="dark">
<Toolbar />
</ThemeContext.Provider>
);
}
}
// 中间的组件再也不必指明往下传递 theme 了。
function Toolbar(props) {
return (
<div>
<ThemedButton />
</div>
);
}
class ThemedButton extends React.Component {
// 指定 contextType 读取当前的 theme context。
// React 会往上找到最近的 theme Provider,然后使用它的值。
// 在这个例子中,当前的 theme 值为 “dark”。
static contextType = ThemeContext;
render() {
return <Button theme={this.context} />;
}
}// 从官方的实例来看,并没有差多少
import React, {useContext} from 'react';
const ThemeContext = React.createContext('light');
export class HookTest extends React.Component {
render() {
// 使用一个 Provider 来将当前的 theme 传递给以下的组件树。
// 无论多深,任何组件都能读取这个值。
// 在这个例子中,我们将 “dark” 作为当前的值传递下去。
return (
<ThemeContext.Provider value="dark">
<Toolbar/>
</ThemeContext.Provider>
);
}
}
// 中间的组件再也不必指明往下传递 theme 了。
function Toolbar(props) {
return (
<div>
<ThemedButton/>
</div>
);
}
function ThemedButton() {
// 指定 contextType 读取当前的 theme context。
// React 会往上找到最近的 theme Provider,然后使用它的值。
// 在这个例子中,当前的 theme 值为 “dark”。
const theme = useContext(ThemeContext);
return <button>{theme}</button>;
}
八、useRef
还是一样,我们先看一下传统react使用ref和使用Hook的区别
import React, {Component} from 'react';
export class HookTest extends Component{
constructor(props){
super(props);
this.state = {
age: 18,
name: "庄子鹏"
}
this.nameInput = null;
}
setAge = () => {
this.setState({
age: this.state.age + 1
})
}
setName = (value) => {
this.setState({
name: value
})
console.log(this.nameInput.value);
//可以获取dom
}
render(){
const {age, name} = this.state;
return (
<>
<div>
我叫{name}, 今天{age}岁
</div>
<button onClick={() => this.setAge()}>加一岁</button>
<input
type="text"
ref={(ele) => this.nameInput = ele} //使用ref回调的方式
onChange={(e) => this.setName(e.target.value)}
/>
</>
)
}
}
import React, {useState, useRef} from 'react';
export function HookTest() {
let [age, setAge] = useState(18);
let [name, setName] = useState("庄某某");
const nameInput = useRef(null);
function go(value){
setName(value);
console.log(nameInput.current.value);
//获取的dom实例都会被包装成一个对象, {current: dom}
}
return (
<>
<div>
我叫{name}, 今天{age}岁
</div>
<button onClick={() => setAge(age + 1)}>加一岁</button>
<input
type="text"
ref={nameInput}
onChange={(e) => go(e.target.value)}
/>
</>
)
}
九、自定义Hook
9.1 自定义hook可以理解为就是使用hook自己定义的一些组件
9.2 自定义hook必须使用use开头,这是一种规则
import React, {useState, useRef, useEffect} from 'react';
function useAge() { //命名必须使用use开头
let [age, setAge] = useState(18);
useEffect(() => {
setInterval(() => {
setAge(age => age + 1);
}, 1000);
}, []);
return [age, setAge];
}
// 每个组件调用同一个 hook,只是复用 hook 的状态逻辑,并不会共用一个状态
function Counter1() {
let [age, setAge] = useAge();
return (
<div>
<button onClick={() => {
setAge(age + 1)
}}>{age}</button>
</div>
)
}
function Counter2() {
let [age, setAge] = useAge();
return (
<div>
<button onClick={() => {
setAge(age + 1)
}}>{age}</button>
</div>
)
}
export function HookTest(){
return (
<>
<Counter1/>
<Counter2/>
</>
)
}