React 新特性 Hooks 讲解及实例(二)

本文是 React 系列的第二篇前端

  1. React 新特性讲解及实例(一)

想阅读更多优质文章请猛戳GitHub博客,一年百来篇优质文章等着你!react

什么是 Hooks

Hook 是 React 16.8 的新增特性。它可让你在不编写 类组件 的状况下使用 state 以及其余的 React 特性。git

类组件的不足

状态逻辑复用难github

  • 缺乏复用机制
  • 渲染属性和高阶组件致使层级冗余

趋向复杂难以维护npm

  • 生命周期函数混杂不相干逻辑
  • 相干逻辑分散在不一样生命周期

this 指向困扰segmentfault

  • 内联函数过分建立新句柄
  • 类成员函数不能保证 this

Hooks 优点

优化类组件的三大问题数组

  • 函数组件无 this 问题
  • 自定义 Hook 方便复用状态逻辑
  • 反作用的关注点分离

使用 State Hook

import React, {Component} from 'react'

class App extends Component {
  state = {
    count: 0
  };
  render() {
    const {count} = this.state;
    return (
      <button type="button"
        onClick={() => {
          this.setState({
            count: count + 1
          })
        }}
      >Click({count})</button>
    )
  }
}
export default App;

以上代码很好理解,点击按钮让 count 值加 1dom

接下来咱们使用 useState 来实现上述功能。函数

import React, {useState} from 'react'

function App () {
  const [count, setCount] = useState(0)
  return (
    <button type="button"
      onClick={() => {setCount(count + 1) }}
    >Click({count})</button>
  )
}

在这里,useState 就是一个 Hook。经过在函数组件里调用它来给组件添加一些内部 state,React 会在重复渲染时保留这个 state学习

useState 会返回一对值:当前状态和一个让你更新它的函数。你能够在事件处理函数中或其余一些地方调用这个函数。它相似 class 组件的 this.setState,可是它不会把新的 state 和旧的 state 进行合并。useState 惟一的参数就是初始 state

useState 让代码看起来简洁了,可是咱们可能会对组件中,直接调用 useState 返回的状态会有些懵。既然 userState 没有传入任何的环境参数,它怎么知道要返回的的是 count 的呢,并且仍是这个组件的 count 不是其它组件的 count

初浅的理解: useState 确实不知道咱们要返回的 count,但其实也不须要知道,它只要返回一个变量就好了。数组解构的语法让咱们在调用 useState 时能够给 state 变量取不一样的名字。

useState 怎么知道要返回当前组件的 state?

由于 JavaScript 是单线程的。在 useState 被调用时,它只能在惟一一个组件的上下文中。

有人可能会问,若是组件内有多个 usreState,那 useState 怎么知道哪一次调用返回哪个 state 呢?

这个就是按照第一次运行的次序来顺序来返回的。

接着上面的例子咱们在声明一个 useState:

...
const [count, setScount] = useState(0)
const [name, setName] = useState('小智')
...

而后咱们就能够判定,之后APP组件每次渲染的时候,useState 第一次调用必定是返回 count,第二次调用必定是返回 name。不信的话来作个实验:

let id = 0

function App () {
  let name,setName;
  let count,setCount;
  
  id += 1;
  if (id & 1) {
    // 奇数
    [count, setCount] = useState(0)
    [name, setName] = useState('小智')
  } else {
    // 偶数
    [name, setName] = useState('小智')
    [count, setCount] = useState(0)
  }

  return (
    <button type="button"
      onClick={() => {setCount(count + 1) }}
    >
      Click({count}), name ({name})
    </button>
  )
}

首先在外部声明一个 id,当 id为奇数和偶数的时候分别让 useState 调用方式相反,运行会看到有趣的现象。

图片描述

当前版本若是写的顺序不一致就会报错。

会发现 countname 的取值串了。咱们但愿给 count 加 1,如今却给 name 加了 1,说明 setCount 函数也串成了 setName 函数。

为了防止咱们使用 useState 不当,React 提供了一个 ESlint 插件帮助咱们检查。

优化点

经过上述咱们知道 useState 有个默认值,由于是默认值,因此在不一样的渲染周期去传入不一样的值是没有意义的,只有第一次传入的才有效。以下所示:

...
const defaultCount = props.defaultCount || 0
const [count, setCount] = useState(defaultCount)
...

state 的默认值是基于 props,在 APP 组件每次渲染的时候 const defaultCount = props.defaultCount || 0 都会运行一次,若是它复杂度比较高的话,那么浪费的资料确定是可观的。

useState 支持传入函数,来延迟初始化:

const [count, setCount] = useState(() => {
  return props.defaultCount || 0
})

使用 Effect Hook

Effect Hook 可让你在函数组件中执行反作用操做。数据获取,设置订阅以及手动更改 React 组件中的 DOM 都属于反作用。无论你知不知道这些操做,或是"反作用"这个名字,应该都在组件中使用过它们。

反作用的时机

  • Mount 以后 对应 componentDidMount
  • Update 以后 对应 componentDidUpdate
  • Unmount 以前 对应 componentWillUnmount

如今使用 useEffect 就能够覆盖上述的状况。

为何一个 useEffect 就能涵盖 Mount,Update,Unmount 等场景呢。

useEffect 标准上是在组件每次渲染以后调用,而且会根据自定义状态来决定是否调用仍是不调用。

第一次调用就至关于componentDidMount,后面的调用至关于 componentDidUpdateuseEffect 还能够返回另外一个回调函数,这个函数的执行时机很重要。做用是清除上一次反作用遗留下来的状态。

clipboard.png

好比一个组件在第三次,第五次,第七次渲染后执行了 useEffect 逻辑,那么回调函数就会在第四次,第六次和第八次渲染以前执行。严格来说,是在前一次的渲染视图清除以前。若是 useEffect 是在第一次调用的,那么它返回的回调函数就只会在组件卸载以前调用了,也就是
componentWillUnmount

若是你熟悉 React class 的生命周期函数,你能够把 useEffect Hook 看作 componentDidMountcomponentDidUpdatecomponentWillUnmount 这三个函数的组合。

举粟说明一下:

class App extends Component {
  state = {
    count: 0,
    size: {
      width: document.documentElement.clientWidth,
      height: document.documentElement.clientHeight
    }
  };
  onResize = () => {
    this.setState({
      size: {
        width: document.documentElement.clientWidth,
        height: document.documentElement.clientHeight
      }
    })
  }
  componentDidMount () {
    document.title = this.state.count;
    window.addEventListener('resize', this.onResize, false)
  }
  componentWillMount () {
    window.removeEventListener('resize', this.onResize, false)
  }
  componentDidUpdate () {
    document.title = this.state.count;
  }
  render() {
    const {count, size} = this.state;
    return (
      <button type="button"
        onClick={() => {this.setState({count: count + 1})}}
      >
        Click({count})
        size: {size.width}x{size.height}
      </button>
    )
  }
}

上面主要作的就是网页 title 显示count 值,并监听网页大小的变化。这里用到了componentDidMountcomponentDidUpdate 等反作用,由于第一次挂载咱们须要把初始值给 title, 当 count 变化时,把变化后的值给它 title,这样 title 才能实时的更新。

注意,咱们须要在两个生命周期函数中编写重复的代码。

这边咱们容易出错的地方就是在组件结束以后要记住销毁事件的注册,否则会致使资源的泄漏。如今咱们把 App 组件的反作用用 useEffect 实现。

function App (props) {
  const [count, setCount] = useState(0);

  const [size, setSize] = useState({
    width: document.documentElement.clientWidth,
    height: document.documentElement.clientHeight
  });

  const onResize = () => {
    setSize({
        width: document.documentElement.clientWidth,
        height: document.documentElement.clientHeight
      }
    )
  }

  useEffect(() => {
    document.title = count;
  })

  useEffect(() => {
    window.addEventListener('resize', onResize, false);
    return () => {
      window.removeEventListener('resize', onResize, false)
    }
  }, [])

  return (
    <button type="button"
      onClick={() => {setCount(count + 1) }}
    >
      Click({count})
      size: {size.width}x{size.height}
    </button>
  )
}

对于上述代码的第一个 useEffect,相比类组件,Hooks 不在关心是 mount 仍是 update。用useEffect统一在渲染后调用,就完整追踪了 count 的值。

对于第二个 useEffect,咱们能够经过返回一个回调函数来注销事件的注册。回调函数在视图被销毁以前触发,销毁的缘由有两种:从新渲染和组件卸载

这边有个问题,既然 useEffect 每次渲染后都执行,那咱们每次都要绑定和解绑事件吗?固然是彻底不须要,只要使用 useEffect 第二个参数,并传入一个空数组便可。第二个参数是一个可选的数组参数,只有数组的每一项都不变的状况下,useEffect 才不会执行。第一次渲染以后,useEffect 确定会执行。因为咱们传入的空数组,空数组与空数组是相同的,所以 useEffect 只会在第一次执行一次。

这也说明咱们把 resize 相关的逻辑放在一直写,不在像类组件那样分散在两个不一样的生命周期内。同时咱们处理 title 的逻辑与 resize 的逻辑分别在两个 useEffect 内处理,实现关注点分离。

咱们在定义一个 useEffect,来看看经过不一样参数,第二个参数的不一样做用。

...
useEffect(() => {
  console.log('count:', count)
}, [count])
...

第二个参数咱们传入 [count], 表示只有 count 的变化时,我才打印 count 值,resize 变化不会打印。

运行效果以下:

图片描述

第二个参数的三种形态,undefined,空数组及非空数组,咱们都经历过了,可是我们没有看到过回调函数的执行。

如今有一种场景就是在组件中访问 Dom 元素,在 Dom元素上绑定事件,在上述的代码中添加如下代码:

...
 const onClick = () => {
  console.log('click');
 }

 useEffect(() => {
   document.querySelector('#size').addEventListener('click', onClick, false);
 },[])
 
  return (
    <div>
    ...
    <span id="size">size: {size.width}x{size.height}</span>
  </div>
)

新增一个 DOM 元素,在新的 useEffect 中监听 span 元素的点击事件。

运行效果:

图片描述

假如咱们 span 元素能够被销毁重建,咱们看看会发生什么状况,改造一下代码:

return (
  <div>
  ...
  </button>
  {
    count%2
    ? <span id="size">我是span</span>
    : <p id='size'>我是p</p>
  }

</div>

运行效果:

图片描述

能够看出一旦 dom 元素被替换,咱们绑定的事件就失效了,因此我们始终要追踪这个dom 元素的最新状态。

使用 useEffect ,最合适的方式就是使用回调函数来处理了,同时要保证每次渲染后都要从新运行,因此不能给第二次参数设置 [],改造以下:

useEffect(() => {
 document.querySelector('#size').addEventListener('click', onClick, false);
   return () => {
     document.querySelector('#size').removeEventListener('click', onClick, false);
   }
})

运行结果:

图片描述

参考

  1. React 官方文档
  2. 《React劲爆新特性Hooks 重构去哪儿网》

交流

  1. React 官方文档
  2. 《React劲爆新特性Hooks 重构去哪儿网》

干货系列文章汇总以下,以为不错点个Star,欢迎 加群 互相学习。

https://github.com/qq44924588...

我是小智,公众号「大迁世界」做者,对前端技术保持学习爱好者。我会常常分享本身所学所看的干货,在进阶的路上,共勉!

关注公众号,后台回复福利,便可看到福利,你懂的。

clipboard.png

相关文章
相关标签/搜索