React Hooks的深入学习
之前有在React Hooks基础这篇文章中浅浅谈了一下Hooks,这篇文章就把新旧知识点进行一个整合,更加深入地去学习React Hooks。
React Hooks是React 16.8 版本推出的新特性,目的在于解决React的状态共享与生命周期管理混乱的问题。React Hooks的出现,就标志着React不会存在无状态组件的情况,只有类组件和函数组件。
一、基础Hook
1. useState()
基础的用法这里就不细讲了,这里大致过一下~
useState() 用于启用函数组件中的状态,第一个参数是状态的初始值。
[state, setState] = useState(initialValue):数组中是状态值和更新状态函数。
一个组件中可以有多个状态。
当初始状态需要昂贵的性能方面操作时,可以在初始化的时候进行:
function Demo() {
const [state, setState] = useState(function demo() {
//expensive operation
})
}
函数demo()仅在初始化时渲染一次,不参与后续组件的渲染,可以利用这一特点提升组件性能。
关于过时状态的处理:
function Demo() {
const [count, setCount] = useState(0)
const handleClick = () => {
setTimeout(function delay() {
setCount(count + 1) //改为:setCount(count => count + 1)
}, 3000)
}
return <div>{count}</div>
}
此时的count数与我们想要的结果不一致,因为delay是一个过时的闭包,捕获的是一个过时的变量,可以使用函数方法来更新状态,改为 setCount(count => count + 1)。
关于复杂状态的管理,会在useReducer中讲到。
2. useEffect()
正常情况下,网络请求、模块订阅以及DOM操作都是属于副作用操作的范畴,官方并不建议在函数体中书写副作用代码,而 Effect Hook 就是用于解决这一问题的,它可以允许你在函数组件中执行一些副作用操作。
可以将 useEffect Hook 看作是componentDidMount、componentDidUpdate 和 componentWillUnmount 的组合。
在class组件中,我们一般希望组件在加载和更新时执行同样的操作,所以导致了我们需要在两个生命周期函数中编写相同的代码。而 useEffect() 一般情况下在第一次渲染之后和每次更新之后都会执行,不需要考虑是挂载还是更新,并且React在保证运行effect的同时,DOM都已经更新完毕。
useEffect 是一个接受两个参数的函数,第一个参数为一个叫 effect 的函数,在组件第一次渲染时被执行,第二个参数是一个存储依赖关系的数组。
关于在async await时报错:
每个async函数都会默认返回一个隐式的promise,但是useEffect不应该返回任何内容,可以采用不直接调用async来解决:
function Demo() {
const [data, setData] = useState(//根据类型定义)
useEffect(() => {
const getData = async () => {
const res = await axios('接口')
setData(res.data)
}
getData()
}, [])
return (//省略)
}
3. useContext()
Context 提供了一个无需为每层组件手动添加props就能在组件树间进行数据传递的方法。
使用 React Context API,在组件外部建立一个 Context。
import React, { useContext } from "react";
import ReactDOM from "react-dom";
import "./styles.css";
const AppContext = React.createContext({});
useContext()钩子函数用来引入 Context 对象,从中获取username属性。
const Navbar = () => {
const { username } = useContext(AppContext)
return (
<div className="navbar">
<p>AwesomeSite</p>
<p>{username}</p>
</div>
)
}
const Messages = () => {
const { username } = useContext(AppContext)
return (
<div className="messages">
<h1>Messages</h1>
<p>1 message for {username}</p>
<p className="message">useContext is awesome!</p>
</div>
)
}
封装组件代码
AppContext.Provider提供了一个 Context 对象,这个对象可以被子组件共享
function App() {
return (
<AppContext.Provider value={{
username: 'superawesome'
}}>
<div className="App">
<Navbar />
<Messages />
</div>
</AppContext.Provider>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
4. useReducer()
useReducer主要用于在某种复杂环境下替换useState,例如包含复杂逻辑的state并且包含多个子值,或是后面的state依赖前面的state等。
useReducer语法如下:
const[state, dispatch] = useReducer(reducer, initialArg, init)
关于复杂状态的管理,使用useState:
function Demo() {
const [person, setPerson] = useState([{name: 'Jack'}])
const addPerson = p => setPerson([...p, person])
const removePerson = index => setPerson([
...person.slice(0, index),
...person.slice(index + 1)
])
return (
// 此处省略
)
}
更好的解决方案是将复杂的状态管理提取到 reducer 中:
function reducer(state, action) {
switch (action.type) {
case 'add':
return [...state, ation.item];
case 'remove':
return [
...state.slice(0, action.index),
...state.slice(action.index + 1)
];
default:
throw new Error();
}
}
function Demo() {
const [state, dispatch] = useReducer(reducer, [{name: 'Jack'}])
return (
// dispatch({ type: 'add', item: person })
// dispatch({ type: 'remove', item: index })
)
}
5. useMemo()
在类组件中,每次状态的更新都会触发组件树的重绘,带来不必要的开销。同样,在函数组件中,为了避免useState每次渲染造成的开销, React Hooks推出了useMemo函数。
useMemo之所以能带来性能上的提升,是因为在依赖不变的情况下,会返回相同的引用,避免子组件进行无意义的重复渲染。
下面代码是使用普通useState实现的功能:
function Demo() {
const [count, setCount] = useState(1)
const [value, setValue] = useState('')
function expensive() {
let sum = 0
for (let i = 0; i < count * 10; i++) {
sum += i
}
return sum
}
return (
<>
{count} : {expensive()}
<button onClick={() => setCount(count + 1)}>+</button>
<input value={value} onChange={e => setValue(e.target.value)} />
</>
)
}
在上面的例子中,修改count和value的值,都会触发expensive方法,但是expensive只是依赖于count,所以更新value是没必要执行expensive的。
可以使用useMemo来优化:
function Demo() {
const [count, setCount] = useState(1)
const [value, setValue] = useState('')
const expensive = useMemo(() => {
let sum = 0
for (let i = 0; i < count * 10; i++) {
sum += i
}
return sum
}, [count])
return (
<>
{count} : {expensive()}
<button onClick={() => setCount(count + 1)}>+</button>
<input value={value} onChange={e => setValue(e.target.value)} />
</>
)
}
这样经过处理后,只会在count更新时触发expensive方法,减少了性能上的开销。
6. useCallback()
和useMemo一样,useCallback 也是用来减少性能开销的,即当依赖的状态改变时,才会调用回调函数。但与之不同的是,useMemo 是用来缓存计算结果的数值的,而 useCallback 缓存的是函数。
一般来说,父组件发生更新,那么子组件也会执行更新,但是在大多数情况下,子组件的更新是没有必要的。所以可以使用useCallback返回缓存的函数,并把这个缓存的函数作为props传递给子组件。
function Parent() {
const [count, setCount] = useState(1)
const [value, setValue] = useState('')
const callback = useCallback(() => {
return count
}, [count])
return (
<>
{count}
<Child callback={callback} />
<button onClick={() => setCount(count + 1)}>+</button>
<input value={value} onChange={e => setValue(e.target.value)} />
</>
)
}
function Child({callback}) {
const [count, setCount] = useState(() => callback())
useEffect(() => {
setCount(callback())
}, [callback])
}
return (
<div>{count}</div>
)
7. useRef()
Ref的主要作用是获取实例或者DOM元素,创建Ref主要有两种方式:creatRef、useRef。
用 creatRef 创建的 Ref 每次渲染都会返回一个新的引用,而 useRef 每次渲染都会返回相同的引用。
使用 creatRef 的方式创建 Ref 主要是类组件:
class Demo extends React.Component {
constructor(props) {
super(props)
this.myRef = React.creatRef()
}
componentDidMount() {
this.myRef.current.focus()
}
render() {
return <input ref={this.myRef} type='text' />
}
}
使用 useRef 创建 Ref:
function Demo() {
const myRef = useRef(null)
useEffect(() => {
myRef.current.focus()
}, [])
return <input ref={myRef} type='text' />
}
二、自定义Hook
在React中,自定义Hook是一个以use开头的函数,函数内部可以调用其他的hook,并且使用自定义hook时,入参和返回值都是可以自定义的,没有其他特殊约定。
定义一个自己的hook:
function useMyHooks() {
const [demo, setDemo] = useState()
useEffect(() => {
//省略
}, [])
// return
}
使用这个hook:
const a = useMyHooks()