Hook 是 React 16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。同时,它是100% 向后兼容的,不包含任何破坏性改动。
在我们讲解React Hook之前,先讲一下为什么要使用它,就好比谈对象,你得先搞清楚你喜欢他什么。我们知道,在没有 hook 之前,我们写一个 react 项目总是避免不了下面的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
class Example extends React.Component { constructor(props) { super(props); this.state = { count: 0 }; } render() { return ( <div> <p>You clicked {this.state.count} times</p> <button onClick={() => this.setState({ count: this.state.count + 1 })}> Click me </button> </div> ); } } |
我想,对于很多做前端的朋友而言,如果学过后端语言还好,因为他们对类有一个明确的概念,对构造器和继承的理解也很容易,但对于很多直接就开始做前端的朋友(比如:非科班出生的一些自学者),突然让他们接受一个类的概念是很难适应的,虽然 ES6 已经有了 class。但是,如果你用 hook,就可以摆脱 class 了,上面的代码就变成了下面这样:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
import React, { useState } from 'react'; function Example() { // 声明一个叫 "count" 的 state 变量 const [count, setCount] = useState(0); return ( <div> <p>You clicked {count} times</p> <button onClick={() => setCount(count + 1)}> Click me </button> </div> ); } |
少了extends,class,constuctor,super等关键字,是不是感觉一下子轻松了很多,同时,onClick 的代码也精简了很多。并且,对于前端来说,一个组件实现的是一个功能,用 function 来定义一个组件,比起用 class 来说,更符合语境。
当然,这只是其中的一个点,它的好处还有很多:
Hook 使你在无需修改组件结构的情况下复用状态逻辑,这使得在组件间或社区内共享 Hook 变得更便捷;
Hook 将组件中相互关联的部分拆分成更小的函数(比如设置订阅或请求数据),而并非强制按照生命周期划分。你还可以使用 reducer 来管理组件的内部状态,使其更加可预测。
Hook 使你在非 class 的情况下可以使用更多的 React 特性,让你的代码远离class。
废话到此结束,接下来,就分别讲讲各个 hook 的用法和作用。不过要记住一点,使用每个 hook 都得先从 react (16.8+版本) 中引入
1 |
import { useState } from 'react'; |
1、useState
上面的代码已经展示过它的功能了,useState 通过在函数组件里调用它来给组件添加一些内部 state,React 会在重复渲染时保留这个 state。useState 会返回一对值:当前状态和一个让你更新它的函数,你可以在事件处理函数中或其他一些地方调用这个函数。它类似 class 组件的 this.setState,但是它不会把新的 state 和旧的 state 进行合并。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
import React, { useState } from 'react'; function Example() { // 声明一个叫 "count" 的 state 变量 const [count, setCount] = useState(0); return ( <div> <p>You clicked {count} times</p> <button onClick={() => setCount(count + 1)}> Click me </button> </div> ); } |
1 2 3 4 5 |
const [ obj,setObj ] = useState({ a: 1 }), const [ arr,setArr ] = useState([ 1, 2 ]), const [ count, setCount ] = useState(() => { return props.count || 0 }) |
1 2 3 |
setCount(count){ return count + 1 } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
function App (props) { let count, setCount let sum, setSum if (count > 2) { [ count, setCount ] = useState(0) [ sum, setSum ] = useState(10) } else { [ sum, setSum ] = useState(10) [ count, setCount ] = useState(0) } return ( <div> 点击次数: { count } 总计:{ sum } <button onClick={() => { setCount(count + 1); setSum(sum - 1)}}>点我</button> </div> ) } |
2、useEffect
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
// 不使用useEffect class App extends PureComponent { componentDidMount() { document.title = count } componentDidUpdate() { document.title = count } } // 使用useEffect function App () { useEffect(() => { document.title = count }) } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
function App () { const [ count, setCount ] = useState(0) const [ width, setWidth ] = useState(document.body.clientWidth) const onChange = () => { setWidth(document.body.clientWidth) } useEffect(() => { // 相当于 componentDidMount window.addEventListener('resize', onChange, false) return () => { // 相当于 componentWillUnmount window.removeEventListener('resize', onChange, false) } }, []) useEffect(() => { // 相当于 componentDidUpdate document.title = count }) useEffect(() => { console.log(`count change: count is ${count}`) }, [ count ]) return ( <div> 页面名称: { count } 页面宽度: { width } <button onClick={() => { setCount(count + 1)}}>点我</button> </div> ) } |
3、useMemo
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
function App(props) { const { pullUp, pullDown, onScroll } = props; // 对传入的上拉和下拉函数进行防抖处理 let pullUpDebounce = useMemo(() => { return debounce(pullUp, 300) }, [pullUp]); let pullDownDebounce = useMemo(() => { return debounce(pullDown, 300) }, [pullDown]); // 绑定scrollEnd方法 useEffect(() => { bScroll.on('scrollEnd', () => { //判断是否滑动到了底部 if(bScroll.y <= bScroll.maxScrollY + 100){ pullUpDebounce(); } }); return () => { bScroll.off('scrollEnd'); } }, []); // 绑定touchEnd方法 useEffect(() => { bScroll.on('touchEnd', (pos) => { //判断用户的下拉动作 if(pos.y > 50) { pullDownDebounce(); } }); return () => { bScroll.off('touchEnd'); } }, []); } |
4、useCallback
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
// 例1 const onClick = useMemo(() => { return () => { console.log('button click') } }, []) const onClick = useCallback(() => { console.log('button click') }, []) // 例2 const [count1, changeCount1] = useState(0); const [count2, changeCount2] = useState(10); const calculateCount = useCallback(() => { if (count1 && count2) { return count1 * count2; } return count1 + count2; }, [count1, count2]) useEffect(() => { const result = calculateCount(count, count2); message.info(`执行副作用,最新值为${result}`); }, [calculateCount]) |
5、useRef
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
const Children = forwardRef((props, ref) => { <div> <p>{props.title}</p> </div> }) function App () { const [ count, setCount ] = useState(0) // 如果children组件不是一个forwardRef,这里会报错 const childrenRef = useRef(null) // const const onClick = useMemo(() => { return () => { console.log('button click') console.log(childrenRef.current) // 这里可以得到Children实例 setCount((count) => count + 1) } }, []) return ( <div> 点击次数: { count } <!-- ref得添加在一个forwardRef上才行 --> <Children ref={childrenRef} count={count}></Children> <button onClick={onClick}>点我</button> </div> ) } |
2、在函数组件中的一个全局变量,不会因为重复 render 而重复申明, 类似于类组件的 this.xxx。有些情况下,我们需要保证函数组件每次 render 之后,某些变量不会被重复申明,比如说 Dom 节点,定时器的 id 等等,在类组件中,我们完全可以通过给类添加一个自定义属性来保留,比如说 this.xxx, 但是函数组件没有 this,自然无法通过这种方法使用,有的朋友说,我可以使用 useState 来保留变量的值,但是 useState 会触发组件 render,在这里完全是不需要的,我们就需要使用 useRef 来实现了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
function App () { const [ count, setCount ] = useState(0) // 注意,这里的ref并没有指定给任何元素 const timer = useRef(null) console.log(timer) let timer2 useEffect(() => { let id = setInterval(() => { setCount(count => count + 1) }, 500) timer.current = id timer2 = id return () => { clearInterval(timer.current) } }, []) const onClickRef = useCallback(() => { clearInterval(timer.current) }, []) const onClick = useCallback(() => { clearInterval(timer2) }, []) return ( <div> 点击次数: { count } <button onClick={onClick}>普通</button> <button onClick={onClickRef}>useRef</button> </div> ) } |
6、useImperativeHandle
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
function Kun (props, ref) { const kun = useRef() const introduce = useCallback (() => { console.log('i can sing, jump, rap, play basketball') }, []) // 这里用useImperativeHandle暴露了一个方法出去 useImperativeHandle(ref, () => ({ introduce: () => { introduce() } })); return ( <div ref={kun}> { props.count }</div> ) } const KunKun = forwardRef(Kun) function App () { const [ count, setCount ] = useState(0) const kunRef = useRef(null) const onClick = useCallback (() => { setCount(count => count + 1) // 这里使用暴露出来的方法,执行子组件的内部逻辑 kunRef.current.introduce() }, []) return ( <div> 点击次数: { count } <KunKun ref={kunRef} count={count}></KunKun> <button onClick={onClick}>点我</button> </div> ) } |
7、useReducer
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
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 ( <> 点击次数: {state.count} <button onClick={() => dispatch({type: 'increment'})}>+</button> <button onClick={() => dispatch({type: 'decrement'})}>-</button> </> ); } |
好了,讲了那么多,我们似乎知道了 Hook 究竟是怎么一回事,说白了,其实 Hook 就是返回包含了更多逻辑的 State 以及改变 State 的方法。
接下来我们来自定义一个自己的钩子,以计数器来为例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
import React, { useState } from 'react'; // 编写我们自己的hook,名字以use开头 function useCounter(initialValue) { // 接受初始化的值生成state const [count, changeCount] = useState(initialValue); // 声明减少的方法 const decrease = () => { changeCount(count - 1); } // 声明增加的方法 const increase = () => { changeCount(count + 1); } // 声明重置计数器方法 const resetCounter = () => { changeCount(0); } // 将count数字与方法返回回去 return [count, { decrease, increase, resetCounter }] } export default function myHooksView() { // 在函数组件中使用我们自己编写的hook生成一个计数器,并拿到所有操作方法的对象 const [count, controlCount] = useCounter(10); return ( <div> 当前数量:{count} <button onClick={controlCount.decrease}>减少</button> <button onClick={controlCount.increase}>增加</button> <button onClick={controlCount.resetCounter}>重置</button> </div> ) } |
本文主要参考文章:
发表评论