react-hooks出来了那么久,看过一点文章,从来没用过,先会用再说把
基础hooks
useState
之前我们写纯UI组件的时候,通常都是写成无状态组件,就是函数的形式,但是有了useSate之后,函数组件也可以有自己的状态啦,话不多说,看怎么用
//引入useState
import {useState} from 'react';
function App() {
//声明状态和改变状态的方法
const [count, setCount] = useState(0);
return (
<div>
<span>patent-count:{count}</span>
</div>
);
}
注意事项:
1、状态声明必须放在函数的最外层,不能在条件语句里面,因为react是通过声明顺序确定状态
2、跟setState不同的是,setState会自动合并对象,而useState要自己去合并
- setState
state = {
name: 'tony',
age: 18
}
this.setState({age: 20})
- useState
const [people, setPeople] = useState({name: 'tony',age: 18});
setPeople({
...people,
age: 20
})
useEffect
useEffect官方文档叫副作用,因为react组件的工作就是渲染UI嘛,副作用我理解就是与渲染无关的,包括数据请求、手动操作DOM、订阅一些事件等等,有了useEffect钩子, 我们可以在函数组件中完成类似类组件中生命周期函数的功能,useEffect接收两个参数,函数和依赖数组的不同组合,实现不同的功能
- 不传第二个参数
//组价初始化,更新都执行副作用
useEffect(() => {
console.log('useEffect run');
})
//如果用生命周期函数实现同样的功能,会比较麻烦一点
componentDidMount() {
console.log('useEffect run');
}
componentDidUpdate() {
console.log('useEffect run');
}
- 第二个参数为空数组
//只在组件初始化执行一次副作用
useEffect(() => {
console.log('useEffect run once');
}, [])
//使用生命周期函数,
componentDidMount() {
console.log('useEffect run once');
}
- 第二个数组参数不为空
//依赖项count改变时执行副作用
useEffect(() => {
console.log(`useEffect run by count`);
}, [count])
- 返回一个函数,解绑副作用
//每次组件更新,都会执行先解绑,再绑定的逻辑
useEffect(() => {
ChatAPI.subscribeToFriendStatus(this.props.id,this.handleStatusChange);
return () => {
ChatAPI.unsubscribeFromFriendStatus(this.props.id,this.handleStatusChange);
}
})
//这样如果订阅时间的依赖id变化,就能重新绑定正确的事件,如果使用生命周期钩子,我们想实现同样的功能,就要增加`componentDidUpdate`逻辑
componentDidMount() {
ChatAPI.subscribeToFriendStatus(this.props.id,this.handleStatusChange);
}
componentDidUpdate() {
ChatAPI.subscribeToFriendStatus(this.props.id,this.handleStatusChange);
}
componentWillUnmount() {
ChatAPI.unsubscribeFromFriendStatus(this.props.id,this.handleStatusChange);
}
注意事项(抄自官网):
与 componentDidMount 或 componentDidUpdate 不同,使用 useEffect 调度的 effect 不会阻塞浏览器更新屏幕,这让你的应用看起来响应更快。大多数情况下,effect 不需要同步地执行。在个别情况下(例如测量布局),有单独的 [useLayoutEffect]Hook 供你使用,其 API 与 useEffect 相同。
useContext
在使用useContext之前,我们先回顾一下之前我们是如何使用react提供的context能力,我们想在组件嵌套层级很深的情况下传递属性,如果不想通过props层层传递,这时候就是context的使用场景,当然redux也能,但是这里我们学习context,就只看一下context怎么用
//父组件App.js
import { createContext} from 'react';
//创建context对象,并导出
const CountContext = createContext(0);
export {CountContext};
function App() {
return(
//包裹子组件
<CountContext.Provider value='123'>
<Child />
</CountContext.Provider>
)
}
//层级很深的子组件
//第一种用法
import {CountContext} from '../App.js';
class GrandChild extends component(){
static contextType = CountContext
return(
<div className='grand-child'>
{this.context}
</div>
)
}
//第二种用法
import {CountContext} from '../App.js';
class GrandChild extends component(){
return(
<CountContext.Consumer>
{value => /* 基于 context 值进行渲染*/}
</CountContext.Consumer>
)
}
如果对以上context的用法已经熟悉了,那么useContext钩子,只不过是增加了一种使用context的方式
//父组件App.js,定义阶段跟之前一毛一样
import { createContext} from 'react';
//创建context对象,并导出
const CountContext = createContext(0);
export {CountContext};
function App() {
return(
//包裹子组件
<CountContext.Provider value='123'>
<Child />
</CountContext.Provider>
)
}
//子组件中,通过useContext接收值
import {CountContext} from '../App.js';
import { useContext } from 'react';
class GrandChild extends component(){
const count = useContext(CountContext);
return(
<div>{count}</div>
)
}
注意事项:
1、子组件会优先使用离自己最近的父组件中定义的context
2、即使中间的组件没有触发重新渲染,父组件的context value发生变化,使用的子孙组件也会触发更新
其他Hooks
memo、useMemo、useCallback
这三个东西放在一起说,因为他们三个做的事情我觉得都一样,分开奖太啰嗦了,,他们都是做缓存的,换句话说,就是控制组件,只在正确的时间重新渲染
/**
*memo是一个高阶函数,用来缓存组件
*第二个参数是一个函数,接受旧值和新值,返回布尔值
*可以通过比较新旧值,决定组件是否重新渲染
*/
import { memo } from 'react';
const isEqual = (prevProps, nextProps) => {
return prevProps.count === nextProps.count;
}
const Child = memo((props) => {
const { children, count } = props;
console.log('child render')
return (
<div>{`${children}:${count}`}</div>
)
}, isEqual)
/**
*useMemo用来缓存变量,重新计算新值变化,组件才会重新渲染
*接收一个函数,和一个依赖项数组,如果不传依赖项,每次都会重新计算值
*
*/
import { useMemo } from 'react';
const Child = (props) => {
const { count } = props;
const newCount = useMemo(() => {
return count;
},[count])
console.log('child render');
return (
<div>
{newCount}
</div>
)
}
/**
*useCallback用来缓存函数
*接收一个函数,第二个参数为依赖项数组
*如果依赖项不变化,传给子组件就是同一个函数,子组件不会重新渲染
*/
//父组件
import { useCallback } from 'react';
function App() {
const handleShowInputValue = useCallback((e) =>{
console.log(e.target.value)
})
return(
<Child onShowInputValue={handleShowInputValue}></Child>
)
}
//子组件
const Child = (props) => {
const { onShowInputValue} = props;
console.log('child render');//不会重复执行
return (
<div className='child'>
<input type="text" onChange={onShowInputValue}/>
</div>
)
}
useReducer
这个钩子乍一看,以为是代替redux的,但是实际上他是useState的替代方案,当更新逻辑比较复杂,或者下一个state依赖于上一个state,使用useReducer, 比如下面的场景:
//对一个状态要做很多不同逻辑,使用useReducer更加优雅
import {useReducer} from 'react';
//定义一个reducer函数,接收state对象,和action
function reducer(state,action){
switch (action.type) {
case 'increment':
return {count: state.count + 1};
case 'decrement':
return {count: state.count - 1};
...
default:
throw new Error();
}
}
function App() {
//声名状态
const [state, dispatch] = useReducer(reducer, {count:0});
return(
<>
<span>{state.count}</span>
<button onClick={() => dispatch({type: 'decrement'})}>-</button>
<button onClick={() => dispatch({type: 'increment'})}>+</button>
</>
);
}
useReducer使用起来跟redux有点类似,但是他和redux有本质的区别:
useReducer是单个组件的状态管理,组件通讯还是需要propsredux是全局状态管理,多组件共享数据
useRef
用过react的一看这个API,大概也能猜到他是干什么的,至少可以知道他可以手动操作DOM节点吧,那在没有hooks之前,还有个creatRef呢,到底有啥区别
//点击button获取焦点
//使用createRef实现
import {createRef} from 'react';
function App() {
const inputEl = createRef();
const handleFoucs () => {
inputEl.current.foucs();
}
return(
<>
<input ref={inputEl}></input>
<button onClick={handleFoucs}>Foucus</button>
</>
)
}
//使用useRef实现同样的功能
import {useRef} from 'react';
function App() {
const inputEl = useRef();
const handleFoucs () => {
inputEl.current.foucs();
}
return(
<>
<input ref={inputEl}></input>
<button onClick={handleFoucs}>Foucus</button>
</>
)
}
从上面的例子看,useRef和createRef作用完全一样,那么为什么还要整个useRef,所以重点是useRef不一样的地方
createRef 与 useRef 的区别
useRef 在 react hook 中的作用, 正如官网说的, 它像一个变量, 它就像一个盒子, 你可以存放任何东西. createRef 每次渲染都会返回一个新的引用,而 useRef 每次都会返回相同的引用
考虑如下场景:
function App() {
const [count, setCount] = useState(0)
const handleALert () => {
setTimeout(() => {
alert(count)
}, 3000)
}
return(
<>
<button onClick={setCount(count+1)}>add</button>
<button onClick={handleALert}>alert</button>
</>
)
}
当我们更新状态的时候, React 会重新渲染组件, 每一次渲染都会拿到独立的 count 状态, 并重新渲染一个 handleALert 函数. 每一个 handleALert 里面都有它自己的 count, alert 出来的值, 就是当时点击时的 count 值.
使用useRef实时获取count值
function App() {
const [count, setCount] = useState(0)
const latestCount = useRef();
useEffect(() => {
latestCount.current = count;
})
const handleALert () => {
setTimeout(() => {
alert(latestCount.current)
}, 3000)
}
return(
<>
<button onClick={setCount(count+1)}>add</button>
<button onClick={handleALert}>alert</button>
</>
)
}
useRef 每次都会返回同一个引用, 所以在 useEffect 中修改的时候 ,在 alert 中也会同时被修改. 这样子, 点击的时候就可以弹出实时的 count 了
上面的问题解决了, 我们继续, 我们希望在界面上显示出上一个 count 的值. 上代码
function App() {
const [count, setCount] = useState(0)
const preCount = useRef();
useEffect(() => {
preCount.current = count;
})
return(
<>
<span>{preCount.current}</span>
<span>{count}</span>
<button onClick={setCount(count+1)}>click me</button>
</>
)
}
useRef创建的是一个普通 Javascript 对象。而 useRef() 和自建一个 {current: ...}对象的唯一区别是,useRef 会在每次渲染时返回同一个 ref 对象。
总结
- useRef 不仅仅是用来管理 DOM ref 的,它可以存放任何变量.
- useRef 可以很好的解决闭包带来的不方便性. 你可以在各种库中看到它的身影, 比如 react-use 中的 useInterval , usePrevious …… 值得注意的是,当 useRef 的内容发生变化时,它不会通知您。更改.current属性不会导致重新呈现。 因为他一直是一个引用
自定义hook
通过自定义 Hook,可以将组件逻辑提取到可重用的函数中
自定义 Hook 是一个函数,其名称以 “
use” 开头,里面是不能使用默认的hook函数内部可以调用其他的 Hook 比如:获取上一个值, 这在实际场景中并不少, 我们尝试把它封装成自定义 hook
import {useRef,useEffect } from 'react';
const usePrevCount = state => {
const ref = useRef();
useEffect(()=>{
ref.current = state;
})
return ref.current;
}
export default usePrevCount;