React Hooks
使用hooks理由
- 高阶组件为了复用,导致代码层级复杂
- 生命周期的复杂
- 写成functional组件,无状态组件 ,因为需要状态,又改成了class,成本高
一、useState(保存组件状态)
const [value, setvalue] = useState("")
相当于类组件中的状态值State
例子:
import React, { useState } from 'react'
export default function App() {
const [value, setvalue] = useState("")
const [list, setlist] = useState(["11", "22", "33"])
const add = () => {
setlist([...list, value])
console.log(list);
setvalue("")
}
const getValue = (e) => {
console.log(e.target.value);
setvalue(e.target.value)
}
const del = (index) => {
const newlist = [...list]
newlist.splice(index, 1)
setlist([...newlist])
}
return (
<div>
<input type="text" value={value} onChange={(e) => {
getValue(e)
}} />
<button onClick={add}>添加</button>
<ul>
{
list.map(
(item, index) => {
return (
<li key={index}>{item} <button onClick={() => {
del(index)
}}>删除</button></li>
)
}
)
}
</ul>
{
!list.length && <div>空</div>
}
</div>
)
}
在使用useState时,我们会结构出两个变量,第一个value是我们的状态,第二个setvalue是我们用来修改状态的唯一方法。
二、useEffect(处理副作用)
Function Component 不存在生命周期,所以不要把 Class Component 的生命周期概念搬过来试图对号入座。useEffect用来实现类似生命周期的。
useEffect(() => {
first
return () => {
second
}
}, [third])
useEfffect中接收两个参数,一个是回调函数,一个是数组,
回调函数中first位置可以写处理逻辑,比如发送请求,初始化,定时器(类似于类组件中componentsDidMount),在return中second位置可以做销毁定时器的操作(类似于componentsWillUnmount),第二个参数数组,放我们使用的依赖的状态也就是useState或者传入的props,如果传空数组的话就只能执行一次。
例子:
import React, { useState, useEffect } from 'react'
export default function App() {
const [name, setname] = useState("xiaoming")
const [password, setpassword] = useState("123456")
useEffect(() => {
setname(name.substring(0, 1).toUpperCase() + name.substring(1))
}, [name])
return (
<div>
App-{name} - {password}
<button onClick={() => {
setname("xiaoli")
}}>click</button>
<button onClick={() => {
setpassword("qwertyu")
}}>修改密码</button>
</div>
)
}
不要对依赖撒谎, 如果你明明使用了某个变量,却没有申明在依赖中,你等于向React撒了谎,后果就是,当依赖的变量改变时,useEffect 也不会再次执行, eslint会报警告。
第一次执行一次后,当name跟新时,会再次更新useEffect,需注意我们在useEffect中setname方法时一直传入新的会一直重新调用渲染页面(状态一直改变),这样会导致循环。
例子:
import React, { useState, useEffect } from 'react'
import axios from 'axios'
export default function App() {
const [type, settype] = useState(1)
return (
<div>
<ul>
<li onClick={() => {
settype(1)
}}>正在热映</li>
<li onClick={() => {
settype(2)
}}>即将上映</li>
</ul>
<FilmList type={type}></FilmList>
</div>
)
}
function FilmList(props) {
const [list, setlist] = useState([])
useEffect(() => {
if (props.type === 1) {
//请求卖座正在热映的数据
console.log("请求卖座正在热映的数据")
axios({
url: "https://m.maizuo.com/gateway?cityId=110100&pageNum=1&pageSize=10&type=1&k=6369301",
headers: {
'X-Client-Info': '{"a":"3000","ch":"1002","v":"5.2.0","e":"16395416565231270166529","bc":"110100"}',
'X-Host': 'mall.film-ticket.film.list'
}
}).then(res => {
console.log(res.data.data.films)
setlist(res.data.data.films)
})
} else {
//请求卖座即将上映的数据
console.log("请求卖座即将上映的数据")
axios({
url: "https://m.maizuo.com/gateway?cityId=110100&pageNum=1&pageSize=10&type=2&k=8077848",
headers: {
'X-Client-Info': '{"a":"3000","ch":"1002","v":"5.2.0","e":"16395416565231270166529","bc":"110100"}',
'X-Host': 'mall.film-ticket.film.list'
}
}).then(res => {
console.log(res.data.data.films)
setlist(res.data.data.films)
})
}
}, [props.type]) //当依赖里的变量更新时会被再次调用useEffect中的回调函数
return (
<ul>
{
list.map(item =>
<li key={item.filmId}>{item.name}</li>
)
}
</ul>
)
}
在这里也要说一下useEffect和useLayoutEffect有什么区别?
简单来说就是调用时机不同, useLayoutEffect 和原来 componentDidMount & componentDidUpdate 一致,在 react完成DOM更新后马上同步调用的代码,会阻塞页面渲染。而 useEffect 是会在整个页面渲染完才会调用的 代码。useLayoutEffect作用在创建DOM之后,渲染之前。
官方建议优先使用 useEffect
在实际使用时如果想避免页面抖动(在 useEffect 里修改DOM很有可能出现)的话,可以把需要操作DOM的代码放在useLayoutEffect里。在这里做点dom操作,这些dom修改会和react做出的更改一起被一次性渲染到屏幕 上,只有一次回流、重绘的代价。
三、useCallback(记忆函数)
防止因为组件重新渲染,导致方法被重新创建 ,起到缓存作用; 只有第二个参数 变化了,才重新声明一次
const handleChange = useCallback(
(evt) => {
settext(evt.target.value)
},
[text]
)
<input onChange={handleChange} value={text} />
//只有text改变后, 这个函数才会重新声明一次,
//如果传入空数组, 那么就是第一次创建后就被缓存, 如果text后期改变了,拿到的还是老的text。
//如果不传第二个参数,每次都会重新声明一次,拿到的就是最新的text.
四、useMemo 记忆组件
useCallback 的功能完全可以由 useMemo 所取代,如果你想通过使用 useMemo 返回一个记忆函数也是完全可以的。相当于Vue中的计算属性。
唯一的区别是:useCallback 不会执行第一个参数函数,而是将它返回给你,而 useMemo 会执行第一个函数并 且将函数执行结果返回给你。所以在前面的例子中,可以返回 handleClick 来达到存储函数的目的。
所以 useCallback 常用记忆事件函数,生成记忆后的事件函数并传递给子组件使用。而 useMemo 更适合经过函数计算得到一个确定的值,比如记忆组件。
import React, { useState,useEffect,useMemo } from 'react'
import axios from 'axios'
export default function Cinema(){
const [mytext, setmytext] = useState("")
const [cinemaList, setcinemaList] = useState([])
useEffect(() => {
axios({
url:"https://m.maizuo.com/gateway?cityId=110100&ticketFlag=1&k=7406159",
method:"get",
headers:{
'X-Client-Info': '{"a":"3000","ch":"1002","v":"5.0.4","e":"16395416565231270166529","bc":"110100"}',
'X-Host': 'mall.film-ticket.cinema.list'
}
}).then(res=>{
setcinemaList(res.data.data.cinemas)
})
}, [])
const getCinemaList = useMemo(() => cinemaList.filter(item=>item.name.toUpperCase().includes(mytext.toUpperCase()) ||
item.address.toUpperCase().includes(mytext.toUpperCase())
), [cinemaList,mytext])
return <div>
{/* {this.state.mytext} */}
<input value={mytext} onChange={(evt)=>{
setmytext(evt.target.value)
}}/>
{
getCinemaList.map(item=>
<dl key={item.cinemaId}>
<dt>{item.name}</dt>
<dd>{item.address}</dd>
</dl>
)
}
</div>
}
useMemo和useCallback两者的区别
- useMemo 缓存的结果是回调函数中return回来的值,主要用于缓存计算结果的值,应用场景如需要计算的状态。
- useCallback 缓存的结果是函数,主要用于缓存函数,应用场景如需要缓存的函数,因为函数式组件每次任何一个state发生变化,会触发整个组件更新,一些函数是没有必要更新的,此时就应该缓存起来,提高性能,减少对资源的浪费;另外还需要注意的是,useCallback应该和React.memo配套使用,缺了一个都可能导致性能不升反而下降。
useRef(获取DOM节点或React元素)
相当于类组件中的 createRef
const myswiper = useRef(null);
<div ref={myswiper}></div>
通过 myswiper.current 获取引用的DOM对象
修改 myswiper.current 不会引发组件重新渲染
useRef 在每次重新渲染后都保持不变,而 createRef 每次都会发生变化
useContext
用法和类组件中的消费者和生产者类似,相当于Vue中的$bus
在根组件中使用 GlobalContext.Provider 组件包裹需要接收数据的后代组件,并通过 value 属性提供要共享的数据。
需要获取公共数据的后代组件:导入useContext,并按需导入根组件中导出的Context对象;调用useContext(第一步中导出的Context) 得到value的值。
案例:
import React, { useState, useEffect, useContext } from 'react'
import axios from 'axios'
import './css/index.css'
const GlobalContext = React.createContext() //创建context对象
export default function App() {
const [filmList, setfilmList] = useState([])
const [info, setinfo] = useState("")
// 请求数据并更新
useEffect(() => {
axios.get(`/test.json`).then(res => {
setfilmList(res.data.data.films)
})
}, [])
return (
<GlobalContext.Provider value={{
call: "打电话",
sms: "短信",
info: info,
changeInfo: (value) => {
setinfo(value)
}
}}>
<div>
{
filmList.map(item =>
<FilmItem key={item.filmId} {...item} ></FilmItem>
)
}
<FilmDetail ></FilmDetail>
</div>
</GlobalContext.Provider>
)
}
/*受控组件*/
function FilmItem(props) {
let { name, poster, grade, synopsis } = props
const value = useContext(GlobalContext)
return <div className="filmitem" onClick={() => {
console.log(synopsis)
value.changeInfo(synopsis)
}}>
<img src={poster} alt={name} />
<h4>{name}</h4>
<div>观众评分:{grade}</div>
</div>
}
function FilmDetail() {
const value = useContext(GlobalContext)
return <div className="filmdetail">
detail-{value.info}
</div>
}
useReducer(组件内部的状态管理方法)
相当于redux ,Vue中的vuex
const [state, dispatch] = useReducer(reducer, intialState)
传入两个参数,reducer是一个函数,用来管理状态的,intialState是初始状态值的。
接收的两个值,state是我们初始化时的状态,dispatch是改变状态的唯一方法。
import React, { useReducer } from 'react'
// 处理函数
const reducer = (prevState, action) => {
// preState是以前的状态值,action是你传过来的参数
// 这里我们应该不能影响原状态,保留住原状态,所以需要新建一个状态
let newstate = { ...prevState }
switch (action.type) {
case "jian":
newstate.count--
return newstate
case "jia":
newstate.count++
return newstate
default:
return prevState
}
}
// 外部对象
const intialState = {
count: 0
}
export default function App() {
const [state, dispatch] = useReducer(reducer, intialState)
return (
<div>
<button onClick={() => {
dispatch({
type: "jian"
})
}}>-</button>
<div>{state.count}</div>
<button onClick={() => {
// 通过调用dispatch这个函数,会把我们的数据传入到 reducer
dispatch({
type: "jia"
})
}}>+</button>
</div>
)
}
用法与 useState 类似,从 useReducer 中得到读接口 state,写接口 dispatch。最后操作时传参给 dispatch 写接口。操作灵活多变,比 useState 好处就是能聚集所有的操作和各种状态量。着重理解这几行代码,读懂。
useReducer 算是 useState 的复杂版
使用 useReducer 分以下步骤:
- 创建初始值的状态initialState
- 创建所有对状态的操作reducer(prevState,action)
- 传给useReducer,得到读和写的接口
- 调用写({‘type’:‘操作类型’})
通常我们使用 useReducer 和 useContext 配合使用
案例:
import React, { useEffect, useContext, useReducer } from 'react'
import axios from 'axios'
import './css/index.css'
const GlobalContext = React.createContext() //创建context对象
const intialState = {
info: "",
filmList: []
}
const reducer = (prevState, action) => {
var newState = { ...prevState }
switch (action.type) {
case "change-filmlist":
newState.filmList = action.value
return newState
case "change-info":
newState.info = action.value
return newState
default:
return prevState
}
}
export default function App() {
const [state, dispatch] = useReducer(reducer, intialState)
useEffect(() => {
axios.get(`/test.json`).then(res => {
// console.log(res.data.data.films)
dispatch({
type: "change-filmlist",
value: res.data.data.films
})
})
}, [])
return (
<GlobalContext.Provider value={
{
state,
dispatch
}
}>
<div>
{/* {this.state.info} */}
{
state.filmList.map(item =>
<FilmItem key={item.filmId} {...item} ></FilmItem>
)
}
<FilmDetail ></FilmDetail>
</div>
</GlobalContext.Provider>
)
}
/*受控组件*/
function FilmItem(props) {
let { name, poster, grade, synopsis } = props
const { dispatch } = useContext(GlobalContext)
// console.log(context)
return <div className="filmitem" onClick={() => {
console.log(synopsis)
// this.props.onEvent(synopsis)
// value.info = "2222222"
// console.log(value)
dispatch({
type: "change-info",
value: synopsis
})
}}>
<img src={poster} alt={name} />
<h4>{name}</h4>
<div>观众评分:{grade}</div>
</div>
}
自定义hooks
- 自定义 Hooks 是一个函数,约定函数名称必须以 use 开头,React 就是通过函数名称是否以 use 开头来判断是不是 Hooks。
- Hooks 只能在函数组件中或其他自定义 Hooks 中使用,否则,会报错!
- 自定义 Hooks 用来提取组件的状态逻辑,根据不同功能可以有不同的参数和返回值(就像使用普通函数一样)
例子:
import React, { useState,useEffect,useMemo } from 'react'
import axios from 'axios'
function useCinemaList(){
const [cinemaList, setcinemaList] = useState([])
useEffect(() => {
axios({
url:"https://m.maizuo.com/gateway?cityId=110100&ticketFlag=1&k=7406159",
method:"get",
headers:{
'X-Client-Info': '{"a":"3000","ch":"1002","v":"5.0.4","e":"16395416565231270166529","bc":"110100"}',
'X-Host': 'mall.film-ticket.cinema.list'
}
}).then(res=>{
setcinemaList(res.data.data.cinemas)
})
}, [])
return {
cinemaList
}
}
function useFilter(cinemaList,mytext){
const getCinemaList = useMemo(() => cinemaList.filter(item=>item.name.toUpperCase().includes(mytext.toUpperCase()) ||
item.address.toUpperCase().includes(mytext.toUpperCase())
), [cinemaList,mytext])
return {
getCinemaList
}
}
export default function Cinema(){
const [mytext, setmytext] = useState("")
const {cinemaList} = useCinemaList()
const {getCinemaList} = useFilter(cinemaList,mytext)
return <div>
{/* {this.state.mytext} */}
<input value={mytext} onChange={(evt)=>{
setmytext(evt.target.value)
}}/>
{
getCinemaList.map(item=>
<dl key={item.cinemaId}>
<dt>{item.name}</dt>
<dd>{item.address}</dd>
</dl>
)
}
</div>
}