最新Hooks教程可看我这篇文章哦:30张图全面剖析React Hooks(十五) - 掘金 (juejin.cn)
1、 React Hooks 介绍
React Hooks是对函数型组件进行增强, 让函数型组件可以存储状态, 可以拥有处理副作用的能力,让开发者在不使用类组件的情况下, 实现相同的功能.
类组件的不足 (Hooks 要解决的问题)
- 缺少逻辑复用机制
为了复用逻辑增加无实际渲染效果的组件,增加了组件层级 显示⼗分臃肿 增加了调试的难度以及运行效率的降低
- 类组件经常会变得很复杂难以维护
将⼀组相干的业务逻辑拆分到了多个生命周期函数中 在⼀个⽣命周期函数内存在多个不相⼲的业务逻辑
- 类成员方法不能保证this指向的正确性
2、React Hooks 使用
Hooks 意为钩子, React Hooks 就是⼀堆钩子函数, React 通过这些钩子函数对函数型组件进⾏增强, 不同的钩子函数提供了不同的功能
seState()
useEffect()
useReducer()
useRef()
useCallback()
useContext()
useMemo()
useState()
使用useState让函数式组件保存状态
import React, { useState } from 'react';
function App() {
const [count, setCount] = useState(0)
return (
<div>
<span>{count}</span>
<button onClick={() => setCount(count + 1)}>+1</button>
</div>
)
}
export default App
useState使用细节
1.接收唯一的参数即状态初始值.初始值可以是任意数据类型
2.返回值为数组.数组中存储状态值和更改状态值的方法.方法名称约定以set开头,后面加上状态名称
3.方法可以被调用多次.用以保存不同状态值
4.参数可以是个函数, 函数返回什么,初始状态就是什么,函数只会被调用一次,用在初始值是动态值的情况
import React, { useState } from 'react';
function App(props) {
// 每次渲染时都会初始化值 无意义 使用useState参数为函数的写法
// const propsCount = props.count || 0
const [count, setCount] = useState(() => {
return props.count || 0
})
const [person, setPerson] = useState({name: "张三", age: 22})
return (
<div>
<span>{count} {person.name} {person.age} {person.sex}</span>
<button onClick={() => setCount(count + 1)}>+1</button>
{/* 传递一个新的状态值替换老的状态值 */}
<button onClick={() => setPerson({name:"李四", age: 30})}>setLisi</button>
{/* 如果只改变一个属性值 前面解构出所有属性 后面是覆盖或者追加的属性 */}
<button onClick={() => setPerson({...person, name: "王五"})}>setWangwu</button>
</div>
)
}
export default App
设置状态值方法的使用细节
设置状态值方法的参数可以是一个值也可以是一个函数
设置状态值方法的方法本身是异步的
import React, { useState } from 'react';
function App() {
const [count, setCount] = useState(0)
function handleCount() {
// 传递函数 count 就是要更改的状态 返回更改后的状态
setCount(count => {
//document.title = count;
//return count + 1
const newCount = count + 1;
document.title = newCount;
return newCount;
})
// 拿到的是上一次的count 证明设置状态的方法是异步的 可以将这个操作放到函数里
// document.title = count
}
return (
<div>
<span>{count}</span>
<button onClick={handleCount}>+1</button>
</div>
)
}
export default App
useReducer()
useReducer是另一种让函数组件保存状态的方式
import React, { useReducer } from 'react';
function App() {
function reducer(state, action) {
switch(action.type) {
case "increment":
return state + 1
case "decrement":
return state - 1
default:
return state
}
}
const [count, dispatch] = useReducer(reducer, 0)
return (
<div>
<button onClick={() => dispatch({type: "increment"})}>+1</button>
<span>{count}</span>
<button onClick={() => dispatch({type: "decrement"})}>-1</button>
</div>
)
}
export default App
useContext()
在跨组件层级获取数据时简化获取数据的代码
import React, { createContext, useContext } from 'react';
const countCotext = createContext()
function App() {
return (
<countCotext.Provider value={100}>
<Foo />
</countCotext.Provider>
)
}
function Foo() {
// 使用useContext简化操作
const value = useContext(countCotext)
return <div>我是Foo组件{value}</div>
// 消费Provider提供的数据
// return (
// <countCotext.Consumer>
// {
// value => {
// return <div>{value}</div>
// }
// }
// </countCotext.Consumer>
// )
}
export default App
useEffect()
让函数型组件拥有处理副作用的能力,类似生命周期函数
可以把useEffect看做componentDidMount, componentDidUpdate和componentWilUnmount这三个函数的组合
import React, { useState, useEffect } from 'react';
import ReactDOM from 'react-dom'
function App() {
const [count, setCount] = useState(0)
// 组件挂载完成和更新完成之后都会执行
// useEffect(() => {
// console.log(333)
// })
// 组件挂载完成之后执行
// useEffect(() => {
// console.log(333)
// }, [])
// 组件被卸载之前执行
useEffect(() => {
return () => {
console.log("组件被卸载了")
}
})
return (
<div>
<span>{count}</span>
<button onClick={() => setCount(count + 1)}>+1</button>
<button onClick={() => ReactDOM.unmountComponentAtNode( document.getElementById('root'))}>卸载组件</button>
</div>
)
}
export default App
useEffect的第二个参数
import React, { useState, useEffect } from 'react';
function App() {
const [count, setCount] = useState(0)
const [person, setPerson] = useState({name: "张三"})
// 只有指定数据发生变化触发useEffect
useEffect(() => {
document.title = count
console.log("count")
}, [count])
return (
<div>
<span>{count} {person.name}</span>
<button onClick={() => setCount(count + 1)}>+1</button>
<button onClick={() => setPerson({name: "李四"})}>setPerson</button>
</div>
)
}
export default App
useEffect结合异步函数
useEffect中的参数函数不能是异步函数,因为useEffect函数要返回清理资源的函数,如果是异步函数就变成了返回Promise
import React, { useEffect } from 'react';
function App() {
useEffect(() => {
// getData().then(data => console.log(data))
(async function() {
const data = await getData()
console.log(data)
})()
}, [])
function getData() {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve("data数据...")
}, 500)
})
}
return <div></div>
}
export default App
useMemo()
useMemo的行为类似Vue中的计算属性,可以监测某个值的变化,根据变化值计算新值
useMemo会缓存计算结果,如果监测值没有发生变化;即使组件重新渲染,也不会重新计算,此行为可以有助于避免在每个渲染上进行昂贵的计算
import React, { useState, useMemo } from 'react';
function App() {
const [count, setCount] = useState(0)
const [bool, setBool] = useState(true)
const result = useMemo(() => {
console.log("useMemo函数执行了")
return count * 2
}, [count])
return (
<div>
<span>{count} {result}</span>
<span>{bool ? "真" : "假"}</span>
<button onClick={() => setCount(count + 1)}>+1</button>
<button onClick={() => setBool(!bool)}>setBool</button>
</div>
)
}
export default App
memo方法
性能优化,如果本组件中的数据没有发生变化,阻止组件更新.类似类组件中的PureComponent和shouldComponentUpdate
import React, { useState, memo } from 'react';
function App() {
const [count, setCount] = useState(0)
return (
<div>
<span>{count}</span>
<button onClick={() => setCount(count + 1)}>+1</button>
<Foo />
</div>
)
}
// 无论自己有没有更改,每次父组件更新都会重新渲染
// function Foo() {
// console.log("Foo组件被重新渲染了");
// return <div>Foo组件</div>
// }
// 使用memo
const Foo = memo(() => {
console.log("Foo组件被重新渲染了")
return <div>Foo组件</div>
})
export default App
useCallback()
性能优化,缓存函数,使组件重新渲染时得到相同的函数实例
import React, { useState, memo, useCallback } from 'react';
function App() {
const [count, setCount] = useState(0)
const resetCount = useCallback(() => {
setCount(0)
}, [setCount])
// 每次App组件渲染都会生成全新resetCount函数传递给Foo组件 导致其重新渲染
// const resetCount = () => {
// setCount(0)
// }
return (
<div>
<span>{count}</span>
<button onClick={() => setCount(count + 1)}>+1</button>
<Foo resetCount={resetCount}/>
</div>
)
}
const Foo = memo((props) => {
console.log("Foo组件被重新渲染了")
return (
<div>
<div>Foo组件</div>
<button onClick={props.resetCount}>resetCount</button>
</div>
)
})
export default App
useRef()
获取DOM元素对象
import React, { useRef } from 'react';
function App() {
const container = useRef()
return (
<div ref={container}>
<button onClick={() => console.log(container)}>获取container</button>
</div>
)
}
export default App
保存数据(跨组件周期)
即使组件重新渲染,保存的数据仍然还在,保存的数据被更改不会触发组件重新渲染
import React, { useState, useEffect, useRef } from 'react';
function App() {
const [count, setCount] = useState(0)
let timerId = useRef();
useEffect(() => {
timerId.current = setInterval(() => {
setCount(count => count + 1)
}, 1000)
}, [])
const stopCount = () => {
console.log(timerId);
clearInterval(timerId.current)
}
return (
<div>
<span>{count}</span>
<button onClick={stopCount}>停止</button>
</div>
)
}
export default App
自定义Hook
自定义Hook是标准的封装和共享逻辑的方式
自定义Hook是一个函数,其名称以use开头
自定义Hook其实就是逻辑和内置Hook的组合
一、
import React, { useState, useEffect } from 'react';
import axios from 'axios'
function App() {
const [post, setPost] = useState({})
useEffect(() => {
axios.get("https://www.fastmock.site/mock/13089f924ad68903046c5a61371475c4/api/post").then(
res => setPost(res.data)
)
}, [])
return (
<div>
<div>{post.title}</div>
<div>{post.body}</div>
</div>
)
}
export default App
使用自定义Hook封装
import React, { useState, useEffect } from 'react';
import axios from 'axios'
function useGetPost() {
const [post, setPost] = useState({})
useEffect(() => {
axios.get("https://www.fastmock.site/mock/13089f924ad68903046c5a61371475c4/api/post").then(
res => setPost(res.data)
)
}, [])
return [post, setPost]
}
function App() {
const [post, setPost] = useGetPost()
return (
<div>
<div>{post.title}</div>
<div>{post.body}</div>
<button onClick={() => setPost({title:"zhangsan", body:123})}>设置文章</button>
</div>
)
}
export default App
二、
import React, { useState } from 'react';
function useUpdateInput(initialValue) {
const [value, setValue] = useState(initialValue)
return {
value,
onChange: event => setValue(event.target.value)
}
}
function App() {
const usernameInput = useUpdateInput("");
const passwordInput = useUpdateInput("");
const submitForm = event => {
event.preventDefault();
console.log(usernameInput.value)
console.log(passwordInput.value)
}
return (
<form onSubmit={submitForm}>
<input type="text" name="username" {...usernameInput}/>
<input type="password" name="password" {...passwordInput}/>
<input type="submit" />
</form>
)
}
export default App
React路由Hooks
react-router-dom路由提供的钩子函数
useState实现原理
import React from "react";
import ReactDOM from "react-dom";
let state = []
let setters = []
let stateIndex = 0
function createSetter(index) {
return function (newState) {
state[index] = newState
render()
}
}
function useState(initialState) {
state[stateIndex] = state[stateIndex] ? state[stateIndex] : initialState
setters.push(createSetter(stateIndex))
let value = state[stateIndex]
let setter = setters[stateIndex]
stateIndex++
return [value, setter]
}
function render() {
stateIndex = 0
ReactDOM.render(<App />, document.getElementById("root"))
}
function App() {
const [count, setCount] = useState(0)
const [name, setName] = useState("张三")
return (
<div>
<span>{count}</span>
<button onClick={() => setCount(count + 1)}>setCount</button>
<span>{name}</span>
<button onClick={() => setName("李四")}>setName</button>
</div>
);
}
export default App;
useEffect实现原理
import React from 'react';
import ReactDOM from 'react-dom';
function render () {
effectIndex = 0;
ReactDOM.render(<App />, document.getElementById('root'));
}
// 上一次的依赖值
let prevDepsAry = [];
let effectIndex = 0;
function useEffect(callback, depsAry) {
// 判断callback是不是函数
if (Object.prototype.toString.call(callback) !== '[object Function]') throw new Error('useEffect函数的第一个参数必须是函数');
// 判断depsAry有没有被传递
if (typeof depsAry === 'undefined') {
// 没有传递
callback();
} else {
// 判断depsAry是不是数组
if (Object.prototype.toString.call(depsAry) !== '[object Array]') throw new Error('useEffect函数的第二个参数必须是数组');
// 获取上一次的状态值
let prevDeps = prevDepsAry[effectIndex];
// 将当前的依赖值和上一次的依赖值做对比 如果有变化 调用callback
let hasChanged = prevDeps ? depsAry.every((dep, index) => dep === prevDeps[index]) === false : true;
// 判断值是否有变化
if (hasChanged) {
callback();
}
// 同步依赖值
prevDepsAry[effectIndex] = depsAry;
effectIndex++;
}
}
function useReducer (reducer, initialState) {
const [state, setState] = useState(initialState);
function dispatch (action) {
const newState = reducer(state, action);
setState(newState);
}
return [state, dispatch];
}
function App() {
function reducer (state, action) {
switch (action.type) {
case 'increment':
return state + 1;
case 'decrement':
return state - 1;
default:
return state;
}
}
const [count, dispatch] = useReducer(reducer, 0);
return <div>
{count}
<button onClick={() => dispatch({type: 'increment'})}>+1</button>
<button onClick={() => dispatch({type: 'decrement'})}>-1</button>
</div>;
}
export default App;
useReducer实现原理
import React from 'react';
import ReactDOM from 'react-dom';
function useReducer (reducer, initialState) {
const [state, setState] = useState(initialState);
function dispatch (action) {
const newState = reducer(state, action);
setState(newState);
}
return [state, dispatch];
}
function App() {
function reducer (state, action) {
switch (action.type) {
case 'increment':
return state + 1;
case 'decrement':
return state - 1;
default:
return state;
}
}
const [count, dispatch] = useReducer(reducer, 0);
return <div>
{count}
<button onClick={() => dispatch({type: 'increment'})}>+1</button>
<button onClick={() => dispatch({type: 'decrement'})}>-1</button>
</div>;
}
export default App;