目录
- 1. 简介
- 2. useState使用
- 3. useEffect使用
- useEffect发起网络请求
1. 简介
在 React 的世界中,有容器组件和 UI 组件之分,在 React Hooks 出现之前,UI 组件我们可以使用函数组件,无状态组件来展示 UI,而对于容器组件,函数组件就显得无能为力,我们依赖于类组件来获取数据,处理数据,并向下传递参数给 UI 组件进行渲染。React在v16.8 的版本中推出了 React Hooks 新特性,Hook是一套工具函数的集合,它增强了函数组件的功能,hook不等于函数组件,所有的hook函数都是以use开头。
使用 React Hooks 相比于从前的类组件有以下几点好处:
- 代码可读性更强,原本同一块功能的代码逻辑被拆分在了不同的生命周期函数中,容易使开发者修改代码时不易去查找,通过 React Hooks 可以将功能代码聚合,方便维护
- 组件树层级变浅,在原本的代码中,我们经常使用 HOC/render/Props 等方式来复用组件的状态,增强功能等,无疑增加了组件树层数及渲染,而在 React Hooks 中,这些功能都可以通过强大的自定义的 Hooks 来实现
使用hook限制:
- hook 只能用在函数组件中,class 组件不行
- 普通函数不能使用 hook,默认只能是函数组件才能用
例外:普通函数名称以 use 开头也可以,(自定义的函数以 use 开头,称为自定义 hook)
- 函数组件内部的函数也不能使用 hook
- hook 函数一定要放在函数组件的第一层,别放在 if/for 中(块级作用域)
- 要求函数组件名称必须首字母大写
2. useState使用
概述:
类组件中有一个状态属性,可以通过此特殊属性完成私有数据的操作。操作此 state 数据可以触发视图更新(this.setState()
)。
函数组件中,从 react16.8 之后,提供一个 hook 函数 useState 方法,它可以模拟出类组件中的状态。
语法:
let [变量,函数] = useState(值|()=>值)
变量就可以得到useState中的值,函数就可以修改值。值的存储使用了闭包。
使用:
import React, { useState } from 'react';
const App = () => {
// 相当于在App函数组件是定义一个state数据,变量名为 count,修改此count的值的方法为setCount
// 写法1:值
// let [count, setCount] = useState(100)
// 有的时候,在项目中的初始数据,要经过一系列的运算才能出来的初始值,这时候就可以使用函数的写法
// 写法2:函数
let [count, setCount] = useState(() => 100)
const addCount = () => {
setCount(count + 1)
}
return (
<div>
<h1>{count}</h1>
<button onClick={addCount}>++++</button>
</div>
);
}
export default App;
处理并发操作:
import React, { useState } from 'react';
const App = () => {
let [count, setCount] = useState(() => 100)
const addCount = () => {
// 并发处理数据的完整性得不到保证
// setCount(count + 1) // 100+1
// setCount(count + 1) // 100+1
// setCount(count + 1) // 100+1
// 并发处理 -- 推荐写法,这样写数据的完整性可靠的
setCount(v => v + 1)
setCount(v => v + 1)
setCount(v => v + 1)
}
return (
<div>
<h1>{count}</h1>
<button onClick={addCount}>++++</button>
</div>
);
}
export default App;
使用useState完成表单项自定义hook函数:
如果我们有两个 input 框需要变为受控组件,我们可以这样写:
import React, { useState } from 'react';
const App = () => {
let [username, setUsername] = useState('')
let [password, setPassword] = useState('')
return (
<div>
{/* 受控组件 */}
<input type="text" value={username} onChange={e => setUsername(e.target.value)} />
<input type="text" value={password} onChange={e => setPassword(e.target.value)} />
</div>
);
}
export default App;
上面的做法让 return 部分的代码太过复杂,我们可以使用自定义 hook 函数来简化这部分的代码:
import React, { useState } from 'react';
// 在react中,定义的函数是以use开头,则认为它就是一个自定义hook函数
// 在自定义hook函数中,可以调用内置hook
function useInput(initialValue = '') {
let [value, setValue] = useState(initialValue)
return { value, onChange: e => setValue(e.target.value.trim()) }
}
const App = () => {
let usernameInput = useInput('')
let passwordInput = useInput('')
return (
<div>
{/* 受控组件 */}
<input type="text" {...usernameInput} />
<input type="text" {...passwordInput} />
</div>
);
}
export default App;
我们还可以使用模块化的思想,将自定义 hook 函数拆分到另一个文件中:
useInput.js:
import { useState } from 'react';
// 在react中,定义的函数是以use开头,则认为它就是一个自定义hook函数
// 在自定义hook函数中,可以调用内置hook
const useInput = (initialValue = '') => {
let [value, setValue] = useState(initialValue)
return { value, onChange: e => setValue(e.target.value.trim()) }
}
export default useInput
App.jsx:
import React from 'react';
import useInput from './hook/useInput';
const App = () => {
let usernameInput = useInput('')
let passwordInput = useInput('')
return (
<div>
<input type="text" {...usernameInput} />
<input type="text" {...passwordInput} />
</div>
);
}
export default App;
3. useEffect使用
概述:
此 hook 可以模拟函数组件的生命周期,函数组件对于在一些生命周期中操作还是无能为力,所以 React 提供了 useEffect 来帮助开发者处理函数组件,来帮助模拟完成一部份的开发中非常常用的生命周期方法。常被别的称为:副作用处理函数。此函数的操作是异步的。
它并不能模拟全部的钩子函数,它只能模拟下面这几个:componentDidMount、componentDidUpdate、componentWillUnmout。
注意:useEffect中不能有返回值,React它要自动回收
比如说下面这种场景,我们希望 console.log 函数中的内容只打印一次,但是每当试图更新的时候,console.log 都会重新执行:
import React, { useState } from 'react';
const App = () => {
let [count, setCount] = useState(100)
const addCount = () => setCount(count + 1)
console.log('App -- 要求此处只打印一次');
return (
<div>
<h3>App组件 --- {count}</h3>
<button onClick={addCount}>++++</button>
</div>
);
}
export default App;
这时候我们就可以使用 useEffect 函数来实现上述需求。
使用:
模拟:componentDidMount componentDidUpdate
import React, { useState, useEffect } from 'react';
const App = () => {
let [count, setCount] = useState(100)
const addCount = () => setCount(count + 1)
// 模拟:componentDidMount componentDidUpdate(可以调用多次)
useEffect(() => {
console.log('App -- useEffect');
})
return (
<div>
<h3>App组件 --- {count}</h3>
<button onClick={addCount}>++++</button>
</div>
);
}
export default App;
模拟:componentDidMount(这种写法可以模拟网络请求)
import React, { useState, useEffect } from 'react';
const App = () => {
let [count, setCount] = useState(100)
const addCount = () => setCount(count + 1)
// 模拟:componentDidMount(可以调用多次)
// 参数2:依赖项,如果为空数据,则只执行1次
// 一般在这样的写法中,完成网络请求
useEffect(() => {
console.log('App -- useEffect');
}, [])
return (
<div>
<h3>App组件 --- {count}</h3>
<button onClick={addCount}>++++</button>
</div>
);
}
export default App;
利用依赖项,模拟componentDidMount、componentDidUpdate
import React, { useState, useEffect } from 'react';
const App = () => {
let [count, setCount] = useState(100)
let [name, setName] = useState('')
const addCount = () => setCount(count + 1)
// 下面这种写法,也可以实现相同的功能
// let [count, setCount] = useState({ num: 100 })
// const addCount = () => setCount({ num: count.num + 1 })
// const addCount = () => setCount(v => ({ num: v.num + 1 }))
// 参数2中依赖项,进行填值,只要依赖项中的值,发生改变,则进行触发
// componentDidMount componentDidUpdate
useEffect(() => {
console.log('App -- useEffect');
// 这里的依赖项中只填了count,所以只有count发生改变,才会触发当前函数
}, [count])
// 对依赖项的使用,可以像下面这样分开写,也可以写在同一个数组中
// useEffect(() => {
// console.log('App -- useEffect');
// }, [name])
return (
<div>
<input value={name} onChange={e => setName(e.target.value)} />
<h3>App组件 --- {count}</h3>
<button onClick={addCount}>++++</button>
</div>
);
}
export default App;
有依赖项,模拟:componentDidMount、componentDidUpdate、componentWillUnmout
import React, { useState, useEffect } from 'react';
const App = () => {
let [count, setCount] = useState(100)
let [name, setName] = useState('')
const addCount = () => setCount(count + 1)
// 有依赖项,只要count改变,则触发
// componentDidMount componentDidUpdate componentWillUnmout
useEffect(() => {
console.log('App -- useEffect');
// 返回回调函数中就是 componetWillUnMount
// 在执行下一个 effect 之前,上一个 effect 就已被清除
return () => {
console.log('componentWillUnmout');
}
}, [count])
return (
<div>
<input value={name} onChange={e => setName(e.target.value)} />
<h3>App组件 --- {count}</h3>
<button onClick={addCount}>++++</button>
</div>
);
}
export default App;
模拟组件销毁
import React, { useState, useEffect } from 'react';
const App = () => {
let [count, setCount] = useState(100)
let [name, setName] = useState('')
const addCount = () => setCount(count + 1)
return (
<div>
<input value={name} onChange={e => setName(e.target.value)} />
<h3>App组件 --- {count}</h3>
{
count > 103 ? null : <Child />
}
<button onClick={addCount}>++++</button>
</div>
);
}
function Child() {
// componentDidMount componentWillUnmout -- 一般模拟组件的销毁
useEffect(() => {
console.log('child -- componentDidMount');
return () => {
console.log('child -- componentWillUnmout');
}
}, [])
return (
<div>
<h3>Child</h3>
</div>
)
}
export default App;
useEffect发起网络请求
import React, { useState, useEffect } from 'react';
import { get } from '@/utils/http'
const App = () => {
let [films, setFilms] = useState([])
// useEffect
// 在之前的版本:第1个参数要求只能是一个普通的函数,没有返回对象,可以返回回调函数,回调函数它模拟生命周期componentWillUnmout
// 还有最后一个问题。在代码中,我们使用async / await从第三方API获取数据。如果你对async/await熟悉的话,你会知道,每个async函数都会默认返回一个隐式的promise。但是,useEffect不应该返回任何内容。这就是为什么会在控制台日志中看到以下警告:
// Warning: useEffect function must return a cleanup function or nothing. Promises and useEffect(async () => …) are not supported, but you can call an async function inside an effect
useEffect(async () => {
let ret = await get('/api/swiper')
setFilms(ret.data)
}, [])
// 如果useEffect顶层不支持 async/await可以使用如下的解决方案
/* useEffect(() => {
; (async function () {
let ret = await get('/api/swiper')
setFilms(ret.data)
})()
}, []) */
return (
<div>
<h3>App组件</h3>
<ul>
{
films.map(item => (
<li key={item.id} > {item.title}</li>
))
}
</ul>
</div >
);
}
export default App;
useEffect 封装网络请求:
useSwiper.js:
import { useEffect, useState } from 'react';
import { get } from '@/utils/http'
const useSwiper = (initialValue = []) => {
let [data, setData] = useState(initialValue)
useEffect(async () => {
let ret = await get('/api/swiper')
setData(ret.data)
}, [])
return data
}
export default useSwiper
App.jsx:
import useSwiper from "./hook/useSwiper";
const App = () => {
let data = useSwiper()
return (
<div>
<h3>App组件</h3>
<ul>
{
data.map(item => (
<li key={item.id} > {item.title}</li>
))
}
</ul>
</div >
);
}
export default App;
封装网络请求时,实现分页:
useGoodFood.js:
import { useEffect, useState } from 'react';
import { get } from '@/utils/http'
const useGoodFood = (initialValue = []) => {
let [data, setData] = useState(initialValue)
let [page, setPage] = useState(1)
const loadData = async () => {
let ret = await get('/api/goodfood?page=' + page)
if (ret.data.length > 0) {
// setData(v => [...v, ...ret.data])
setData(ret.data)
setPage(v => v + 1)
}
}
useEffect(() => {
loadData()
}, [])
return [data, loadData]
}
export default useGoodFood
App.jsx:
import useGoodFood from "./hook/useGoodFood";
const App = () => {
let [data, loadData] = useGoodFood()
return (
<div>
<h3>App组件</h3>
<ul>
{
data.map(item => (
<li key={item.id} > {item.name}</li>
))
}
</ul>
<hr />
<button onClick={() => {
loadData()
}}>下一页</button>
</div >
);
}
export default App;