10 分钟了解 Hooks API

作者: 写代码的海怪 | 来源:发表于2018-12-21 06:53 被阅读74次

    Hooks API 是 React 16.7.0-alpha.0 版本推出的 API,要注意的是只有这个版本才有,别的版本都没有,如果你在 16.7.0 正式版是没有的!我就是被坑了。

    如果你用 create-react-app 来创建应用的,现在的版本会是 16.7.0,一定要改成 16.7.0-alpha.0 才能使用 Hooks API

    useState

    useState API 本质了为了使得函数式组件也能有自身状态而推出的 API。

    以前的函数组件

    假如我们要做一个简单的计数器,如果用以前的函数式组件是不行的,因为函数式组件是没有自身状态的。比如:

    function App()l {
      let count = 0
      let add = function() {}
      return (
        <div>
          <div>{count}</div>
          <div><button onClick={add}>+1</button></div>
        </div>
       )
    }
    

    你可能说 count 不行么?因为每次 render 的时候这个函数就会被执行一次,那么就会执行 let count = 0 ,所以每次都会重置了 count,没用。

    解决方法是用 class 组件,在 this.state 对象里放入 count

    class App extends React.Component {
        constructor(props) {
            super(props)
            this.state = {
                count: 0
            }
        }
    
        add = () => {
            this.setState({
                count: this.state + 1
            })
        }
    
        render() {
            return (
              <div>
                <div>App: {count}</div>
                <div>
                  <button onClick={add}>+1</button>
                </div>
              </div>
            )
        }
    }
    

    烦不烦?写那么多个单词。所以 Hooks API 出现了,它可以给函数式组件拥有自身状态。

    使用 useState

    useState 的作用就是创建一个状态,可以是对象也可以是别的类型,还会创建一个修改这个状态的函数。上面的例子用新 API 可以写成这样:

    function App() {
        // 状态和修改状态的函数
        const [count, setCount] = useState(0)
    
        // Handler
        const add = () => {
            setCount(count + 1)
        }
    
        return (
          <div>
            <div>App: {count}</div>
            <div>
              <button onClick={add}>+1</button>
            </div>
          </div>
        )
    }
    

    是不是感觉清爽很多了?没有什么 constructor, super, render 那么多东西,现在只需要我好好写一个函数就行了。

    注意

    • 不能在函数式组件外面使用 useState,这个很好理解,毕竟定义的就是自己的状态当然在组件自己里使用
    • 数组里的名字可以改,可以是 const [x, y] = useState(0),你自己清楚 x, y 代表什么就好了

    useEffects

    这个 API 就有点难理解了,Effect 的翻译是影响,副作用。这里的 Effects,相当于你使用了别人的东西。

    什么是 Effect

    这里的 Effect 分两种:一种叫 Effects without Cleanup,就是说你拿别人的东西吃,吃完这个东西就没了,不用再去清理,因为东西就在你肚子里呀。另一种叫 Effects with Cleanup,比如你拿别人的书看,看完了还得将书要么扔了,要么还给别人,总之这本书你得处理。

    那么 React Effects 有哪些呢?简单的如:DOM 修改,网络请求,订阅事件,这些都是使用了组件之外的资源,所以每一次的这些行为就是一个 Effect。

    那为毛要搞一个 useEffects 呢?下面就分两种 Effects 情况来说说吧,同时讲下怎么使用 useEffects。

    Effects with Cleanup

    想像一个场景,每次更新 count 之后都要设置 document.title = count,你会怎么做。哎,这个简单,在上面的 add() 函数里直接改不就好了?但是假如这个 count 作为共享数据可以被所有组件修改的,而且有 minus(), divide(), multiple() 等方法去改这个值,那就要重复很多次 document.title = count

    这时候我们可以总结出不管怎样去修改,反正就是修改后就要执行 document.title = count 代码嘛。即然是 count 值每次都修改就会导致这个组件重新渲染,那么那我在生命钩子componentDidUpdatecomponentDidMount 里去改 title不就好了?如:

    class App extends React.Component {
        constructor(props) {
            super(props)
            this.state = { count: 0 }
        }
    
        componentDidMount() {
            document.title = this.state.count
        }
        componentDidUpdate() {
            document.title = this.state.count
        }
        
        render() {
            return (...)
        }
    }
    

    还是那个问题:烦不烦啊,还要我写两次。这时候 React 就推出了 useEffect,即每次渲染(包括 mounted 和 updated)后,即就是说每次执行 render 后,会回调一个函数,这个函数执行你要做的事情。

    可以将上面的改写成这样。

    function App (props) {
        useEffect(() => {
            document.title = count
        })
        return (...)
    }
    

    代码变成6行,舒服~。再回来看看 Effect 的定义,使用组件外的东西,对呀我们使用了 document 对象,这不是组件里的呀。好像也不太需要清理什么东西,因为就设置一个值嘛。所以这就叫 Effects without Cleanup。

    Effects with Cleanup

    有 without 就有 with,什么时候要 Cleanup 呢?那肯定是无端端创建东西的时候嘛,像垃圾回收一样,如果创建的东西不再需要了,那就清掉呗。React 里的例子是添加监听事件喽。

    比如在一个 MyButton 组件开始的时候我要默认添加一个事件中心的监听回调,然后这个组件销毁的时候回收这个回调函数。使用生命钩子会写成这样:

    class MyButton extends React.Component {
        constructor(props) {
            super(props)
            this.state = { isTriggered: false }
        }
    
        componentDidMount() {
            EventHub.on('Trigger event', this.state.isTriggered, () => {
                console.log('Do something')
            })
        }
        componentWillUnmount() {
            EventHub.removeListener('Trigger event', this.state.isTriggered, () => {
                console..log('Listener is removed')
            })
        }
        render() {
            return ( ... )
        }
    }
    

    我们使用了 EventHub,很明显这不是组件里的东西,是属性外面的,所以这也是个 Effect。
    而这次使用 useEffect 就不太一样了,因为我们是要擦屁股的,在组件 unmount 的时候取消监听。而 useEffect 提供了一个很好理解的用法。

    function MyButton(props) {
        useEffect(() => {
            EventHub.on('Trigger event', this.state.isTriggered, () => {
                console.log('Do something')
            })
    
            return function cleanup() {
                EventHub.removeListener('Trigger event', this.state.isTriggered, () => {
                    console.log('listener is removed')
                })
            }
        })
        return (...)
    }
    

    当组件 unmount 的时候就会去执行 cleanup 这个函数,是不是感觉一目了然?这里的函数名不是必须的,只是为了说明这里用来清理而已。

    注意

    1. 代码简洁就不用说太多了,一看就知道谁好用了
    2. 而因为使用生命钩子是同步的,会阻塞页面的渲染,或者组件的销毁也会造成页面卡顿。而 setEffect 里面其实是异步进行的不会有这些影响
    3. 当然 setEffect 也不是全都是好的,比如每次 render 后都会用一个一模一样的函数去替换原来来的回调,以保持状态都是最新的

    当然这只是简单的用法,更多的用法请看官方文档

    相关文章

      网友评论

        本文标题:10 分钟了解 Hooks API

        本文链接:https://www.haomeiwen.com/subject/pwupkqtx.html