最新公告
  • 欢迎您光临网站无忧模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • React Hooks系列(一):常用Api的使用方法

    正文概述 掘金(飞鹤乳业)   2021-04-03   812

    React Hooks系列(一):常用Api的使用方法

    前言

    Hook 是 React 16.8 的新增特性。它解决了函数组件只能做视图渲染的限制,可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。

    优点

    1. 可以避开 class 组件中的 this
    2. 使用自定义hook,逻辑的复用更加简单
    3. 代码更清爽,可以减少 class 组件中使用高阶组件额外创建的多层 dom 嵌套关系

    缺点

    1. 由于 hooks 大多都基于闭包保存状态,常常在一些异步代码或回调中拿不到最新的状态
    2. effect 的依赖项交叉改变时会导致意想不到的错误渲染,需要花更多的时间来解决

    API

    1. useState

    目前前端框架一大特点就是数据驱动视图,所以定义数据项,修改数据项,就是基础中的基础,这里分别用类组件和函数组件分别完成一个小功能,每点击 “+1” 按钮一次,页面上数字加一

    React Hooks系列(一):常用Api的使用方法

    import React, { Component } from 'react'
    import styles from './index.module.css'
    export default class ComA extends Component {
      state = {
        count: 0
      }
      onClick = () => {
        const { count } = this.state
        this.setState({ count: count + 1 })
      }
      render () {
        const { count } = this.state
        return (
          <div className={styles.main}>
            <div className={styles.count}>{count}</div>
            <button className={styles.btn} onClick={this.onClick}>
              +1
            </button>
          </div>
        )
      }
    }
    

    React Hooks系列(一):常用Api的使用方法

    // useState,就是为了解决函数组件中没有状态
    import { useState } from 'react'
    import styles from './index.module.css'
    export default function ComB () {
      // useState函数接收的参数是状态的默认值,其返回值是一个数组,第一个值是该状态的数据项,第二个值就是改变这个状态的方法
      const [count, setCount] = useState(0)
      const onClick = () => {
        setCount(count + 1)
      }
      return (
        <div className={styles.main}>
          <div className={styles.count}>{count}</div>
          <button className={styles.btn} onClick={onClick}>
            +1
          </button>
        </div>
      )
    }
    

    useState小结

    可以看到,有了 hooks 的函数组件代码行数是要更少,而且摆脱了 this,如果想增加状态继续使用 useState 创建就好

    2. useEffect

    有了数据项,那自然需要生命周期在不同时机对数据项修改,useEffect 函数就是函数组件的生命周期,还是刚刚的例子,我们添加一个简单的 useEffect 看看效果

    import { useState, useEffect } from 'react'
    import styles from './index.module.css'
    export default function ComB () {
      const [count, setCount] = useState(0)
      const onClick = () => {
        setCount(count + 1)
      }
      // useEffect 有两个参数第一个参数是函数必填,而且返回值可以没有,如果有必须返回一个函数,第二个参数选填是一个数组
      // 这里先写一个最简单的 effect ,其他参数后面会说到
      useEffect(()=>{
        console.log('111')
      })
      console.log('渲染视图')
      return (
        <div className={styles.main}>
          <div className={styles.count}>{count}</div>
          <button className={styles.btn} onClick={onClick}>
            +1
          </button>
        </div>
      )
    }
    

    刷新页面,发现 “111” 打印出来了,并且 “渲染页面” 在 “111” 之前打印,这不就是妥妥的 componentDidMount()

    React Hooks系列(一):常用Api的使用方法

    我们继续操作,点击几次 “+1” 按钮看看,一共点击三次,打印了三次,看来 componentDidUpdate() 时 也会触发useEffect

    React Hooks系列(一):常用Api的使用方法

    我们删掉无关代码,给effect的第一个参数加一个返回值(必须是函数),打印当前的 count 值

    import { useState, useEffect } from 'react'
    import styles from './index.module.css'
    export default function ComB () {
      const [count, setCount] = useState(0)
      const onClick = () => {
        setCount(count + 1)
      }
      useEffect(() => {
        console.log('111')
        return () => {
          console.log(count)
        }
      })
      return (
        <div className={styles.main}>
          <div className={styles.count}>{count}</div>
          <button className={styles.btn} onClick={onClick}>
            +1
          </button>
        </div>
      )
    }
    

    刷新页面,抛开首次加载打印的第一个 “111”,每点击一次按钮都会打印上一次的 count 值,然后打印 “111”,这意味着每一次触发effect都会执行上一次effect的返回值,这是类组件所没有的

    React Hooks系列(一):常用Api的使用方法

    这时我们移除函数组件(切换至类组件),发现 3 被打印出来了,这个返回值(函数)看来还有 componentWillUnmount() 的作用

    React Hooks系列(一):常用Api的使用方法

    现在给effect添加第二个参数,需要传一个数组,先传一个空数组

    import { useState, useEffect } from 'react'
    import styles from './index.module.css'
    export default function ComB (props) {
      const [count, setCount] = useState(0)
      const onClick = () => {
        setCount(count + 1)
      }
      useEffect(() => {
        console.log('111')
        return () => {
          console.log('222')
        }
      }, [])
      return (
        <div className={styles.main}>
          <div className={styles.count}>{count}</div>
          <button className={styles.btn} onClick={onClick}>
            +1
          </button>
        </div>
      )
    }
    

    刷新页面首次进入,打印 “111” ,之后无论我们怎么点击 “+1”按钮,都没了反应

    React Hooks系列(一):常用Api的使用方法

    之后我们移除函数组件(切换到类组件),“222” 也在移除组件前打印了,看来传入空数组,effect并不会受到数据项变化而触发 ,创建组件移除组件依旧会触发

    React Hooks系列(一):常用Api的使用方法

    接下来给空数组加一项 “count” ,发现所有表现,和 effect 不传第二个参数的表现一模一样

    import { useState, useEffect } from 'react'
    import styles from './index.module.css'
    export default function ComB (props) {
      const [count, setCount] = useState(0)
      const onClick = () => {
        setCount(count + 1)
      }
      useEffect(() => {
        console.log('111')
        return () => {
          console.log('222')
        }
      }, [count])					// 第二个参数不传,相当于传了一个所有数据项所集合的数组
      return (
        <div className={styles.main}>
          <div className={styles.count}>{count}</div>
          <button className={styles.btn} onClick={onClick}>
            +1
          </button>
        </div>
      )
    }
    

    至此,我们知道了,useEffect可以通过给第一个参数添加返回值,添加第二个参数,代替传统类组件的 componentDidMount()、componentWillUnmount()、componentDidUpdate(),我们模拟一个接口请求,做一个列表渲染试试,体会一下

    import { useState, useEffect } from 'react'
    import styles from './index.module.css'
    // 模拟接口
    const getListApi = () => {
      return new Promise((resolve, reject) => {
        setTimeout(() => {
          resolve([
            { id: 1, type: 10, text: 'List1' },
            { id: 2, type: 10, text: 'List2' },
            { id: 3, type: 20, text: 'List3' },
            { id: 4, type: 20, text: 'List4' },
            { id: 5, type: 10, text: 'List5' }
          ])
        }, 100)
      })
    }
    
    export default function ComC () {
      const [list, setList] = useState([])
      // 模拟接口请求渲染数据
      useEffect(async () => {
        const res = await getListApi()
        setList(res)
      }, [])								// 接口只进入页面时请求,第二个参数只需要传空数组
      return (
        <div className={styles.main}>
          <div className={styles.list}>
            {list.length &&
              list.map(val => (
                <div className={styles.line} key={val.id}>
                  {val.text}
                </div>
              ))}
          </div>
        </div>
      )
    }
    

    看起来似乎没问题,列表正常渲染了,可是代码似乎有一些不太对,async声明过的函数其返回值是一个promise,而useEffect第一个参数的返回值是需要一个函数

    React Hooks系列(一):常用Api的使用方法

    打开控制台,发现有警告,建议我们再包一层不要让useEffect的第一个参数的返回值有冲突

    React Hooks系列(一):常用Api的使用方法

    改动一下代码,保证页面不会出现问题,这样一个简单的模拟请求的页面就完成了

    import { useState, useEffect } from 'react'
    import styles from './index.module.css'
    // 模拟接口
    const getListApi = () => {
      return new Promise((resolve, reject) => {
        setTimeout(() => {
          resolve([
            { id: 1, type: 10, text: 'List1' },
            { id: 2, type: 10, text: 'List2' },
            { id: 3, type: 20, text: 'List3' },
            { id: 4, type: 20, text: 'List4' },
            { id: 5, type: 10, text: 'List5' }
          ])
        }, 100)
      })
    }
    
    export default function ComC () {
      const [list, setList] = useState([])
      useEffect(() => {
        getList()
      }, [])								// 接口只进入页面时请求,第二个参数只需要传空数组
      // 抽出 async 函数,避免冲突
      const getList = async () => {
        const res = await getListApi()
        setList(res)
      }
      return (
        <div className={styles.main}>
          <div className={styles.list}>
            {list.length &&
              list.map(val => (
                <div className={styles.line} key={val.id}>
                  {val.text}
                </div>
              ))}
          </div>
        </div>
      )
    }
    

    useEffect小结

    1. useEffect第一个参数(函数)会在创建组件开始执行(第二个参数所绑定的数据项更新也会触发执行),其返回值(函数)会在移除组件前触发(第二个参数所绑定的数据项在下一次变化前也会执行),给函数组件带来了生命周期
    2. useEffect 第一个参数(函数)的返回值一定要是函数,避免 hooks 出现错误
    3. useEffect也可以写多个(顺序执行)
    4. 要避免在已经监听某状态的 effect 中修改这个状态,这样将会使 effect 死循环

    3. useRef

    useRef就是获取dom元素的方法

    // 类组件,this.a 就可以获取这个dom
    import React from 'react'
    class A extends React.Component{
        a = React.createRef()
    	render() { return<div ref={a}></div> }
    }
    
    // useRef,b.current
    import React, { useRef } from 'react'
    function B () {
        const b = useRef(null)
        return <div ref={b}><div>
    }
    

    useRef的使用并不难,绑定之后只需要 .current 就可以获取dom对象,这里想说的是useRef更有用的地方,保存一个任意地方都可以取到的可变值,还记得刚刚的列表渲染的案例吗,现在想加一个功能,点击tab标签根据类别进行筛选展示

    import { useState, useEffect } from 'react'
    import styles from './index.module.css'
    // tab
    const TITLE = [
      { code: 0, text: '全部' },
      { code: 10, text: '类型一' },
      { code: 20, text: '类型二' }
    ]
    // 模拟接口
    const getListApi = () => {
      return new Promise((resolve, reject) => {
        setTimeout(() => {
          resolve([
            { id: 1, type: 10, text: 'List1' },
            { id: 2, type: 10, text: 'List2' },
            { id: 3, type: 20, text: 'List3' },
            { id: 4, type: 20, text: 'List4' },
            { id: 5, type: 10, text: 'List5' }
          ])
        }, 100)
      })
    }
    
    export default function ComC () {
      // tab 选中的索引
      const [checkedIdx, setCheckIdx] = useState(0)
      // 页面展示的 list
      const [list, setList] = useState([])
      // 接口返回的所有列表数据 
      const [AllList, setAllList] = useState([])
      useEffect(() => {
        getList()
      }, [])
      // 接口请求,渲染列表
      const getList = async () => {
        const res = await getListApi()
        setList(res)
        setAllList(res)
      }
      // tab切换,试图更新
      const onClick = idx => {
        setCheckIdx(idx)
        if (idx === 0) return setList(AllList)
        const newList = AllList.filter(val => val.type === TITLE[idx].code)
        setList(newList)
      }
      return (
        <div className={styles.main}>
          <div className={styles.tab}>
            {TITLE.map((val, idx) => (
              <div
                onClick={() => {
                  onClick(idx)
                }}
                key={val.code}
                className={idx === checkedIdx ? styles.checked : ''}
              >
                {val.text}
              </div>
            ))}
          </div>
          <div className={styles.list}>
            {list.length &&
              list.map(val => (
                <div className={styles.line} key={val.id}>
                  {val.text}
                </div>
              ))}
          </div>
        </div>
      )
    }
    
    

    React Hooks系列(一):常用Api的使用方法

    完成这个功能,代码中并没有用到 useRef ,而是增加了一个 AllList 的状态,可是仔细想一下,AllList并没有参与渲染页面,真正渲染页面的是 list ,AllList仅仅是一个提供数据的数据源,完全没有必要单独给它创建一个状态,浪费性能,使用useRef改造一下

    import { useState, useEffect, useRef } from 'react'
    import styles from './index.module.css'
    const TITLE = [
      { code: 0, text: '全部' },
      { code: 10, text: '类型一' },
      { code: 20, text: '类型二' }
    ]
    // 模拟接口
    const getListApi = () => {
      return new Promise((resolve, reject) => {
        setTimeout(() => {
          resolve([
            { id: 1, type: 10, text: 'List1' },
            { id: 2, type: 10, text: 'List2' },
            { id: 3, type: 20, text: 'List3' },
            { id: 4, type: 20, text: 'List4' },
            { id: 5, type: 10, text: 'List5' }
          ])
        }, 100)
      })
    }
    
    export default function ComC () {
      // tab 选中的索引
      const [checkedIdx, setCheckIdx] = useState(0)
      // 页面展示的 list
      const [list, setList] = useState([])
      // 接口返回的所有列表数据
      const AllList = useRef([])													// 修改
      useEffect(() => {
        getList()
      }, [])
       // 接口请求,渲染列表
      const getList = async () => {
        const res = await getListApi()
        setList(res)
        AllList.current = res														// 修改
      }
        // tab切换,试图更新
      const onClick = idx => {
        setCheckIdx(idx)
        if (idx === 0) return setList(AllList.current)								  // 修改
        const newList = AllList.current.filter(val => val.type === TITLE[idx].code)		// 修改
        setList(newList)
      }
      return (
        <div className={styles.main}>
          <div className={styles.tab}>
            {TITLE.map((val, idx) => (
              <div
                onClick={() => {
                  onClick(idx)
                }}
                key={val.code}
                className={idx === checkedIdx ? styles.checked : ''}
              >
                {val.text}
              </div>
            ))}
          </div>
          <div className={styles.list}>
            {list.length &&
              list.map(val => (
                <div className={styles.line} key={val.id}>
                  {val.text}
                </div>
              ))}
          </div>
        </div>
      )
    }
    

    useRef 小结

    useRef 可以用来获取dom对象,其实 useRef 真正的的作用是保存可变值,其类似于在 class 中使用实例字段的方式,减少性能浪费

    4. useMemo、useCallback

    说起性能,合理使用 useMemo、useCallback 可以提高效率,减少性能浪费,先说 useMemo,我们给之前的点击自增1的组件加一个显示时间的功能,初始时间格式为 xx/xx/xx,显示的格式为 xx-xx-xx

    React Hooks系列(一):常用Api的使用方法

    import { useEffect, useState } from 'react'
    import styles from './index.module.css'
    export default function ComB (props) {
      const [count, setCount] = useState(0)
      const [date, setDate] = useState('')
      useEffect(() => {
        setDate('2021/03/31')
      }, [])
      function onClick () {
        setCount(count + 1)
      }
      function formatDate (date) {
        console.log('处理数据')
        return date.replace(/\//g, '-')
      }
      return (
        <div className={styles.main}>
          <div className={styles.count}>{formatDate(date)}</div>
          <div className={styles.count}>{count}</div>
          <button className={styles.btn} onClick={onClick}>
            +1
          </button>
        </div>
      )
    }
    

    看起来没有问题,打印两次 ‘处理数据’ 一次是组件初始化,一次是首次 effect 副作用,这时候我们点击按钮看看会发生什么

    React Hooks系列(一):常用Api的使用方法

    我们发现每次点击 +1 按钮视图更新的时候同样也打印了 ‘处理数据’,其实我们并不需要每次更新视图的时候都处理一次时间,显然这样重复对同一个值多次计算很浪费,这时候就可以使用 useMemo 改造一下

    import { useEffect, useState, useMemo } from 'react'
    import styles from './index.module.css'
    export default function ComB (props) {
      const [count, setCount] = useState(0)
      const [date, setDate] = useState('')
      // useMemo 同样接收两个参数,第一个参数是一个函数 useMemo 的返回值就是这个回调函数的返回值,这个值会被缓存起来
      // 第二个参数是一个数组,数组中每一项变化都会重新执行回调函数缓,没变化则不会多次计算
      const formatDateMemo = useMemo(() => formatDate(date), [date])
      useEffect(() => {
        setDate('2021/03/31')
      }, [])
      function onClick () {
        setCount(count + 1)
      }
      function formatDate (date) {
        console.log('处理数据')
        return date.replace(/\//g, '-')
      }
      return (
        <div className={styles.main}>
          <div className={styles.count}>{formatDateMemo}</div>
          <div className={styles.count}>{count}</div>
          <button className={styles.btn} onClick={onClick}>
            +1
          </button>
        </div>
      )
    }
    

    因为这个返回值被缓存起来了,所有这时候我们多次点击按钮就不会计算多次了,是不是和 vue2 中的计算属性(computed)很像

    React Hooks系列(一):常用Api的使用方法

    同样是这个功能,使用 useCallback 一样可以避免多次渲染

    import React, { useEffect, useState, useCallback } from 'react'
    import styles from './index.module.css'
    export default function ComB (props) {
      const [count, setCount] = useState(0)
      const [date, setDate] = useState('')
      // useCallback 接收两个参数,第一个参数是一个回调函数,useCallback 的返回值就是被缓存起来的这个回调函数
      // 第二个参数同样是个数组,只有数组中某一项变化才会重新缓存
      const formatDateCallback = useCallback(formatDate, [date])
      useEffect(() => {
        setDate('2021/03/31')
      }, [])
      function onClick () {
        setCount(count + 1)
      }
      function formatDate () {
        return date.replace(/\//g, '-')
      }
      return (
        <div className={styles.main}>
          <MyDate formatDate={formatDateCallback}></MyDate>
          <div className={styles.count}>{count}</div>
          <button className={styles.btn} onClick={onClick}>
            +1
          </button>
        </div>
      )
    }
    
    class MyDate extends React.PureComponent{
      render () {
        console.log('组件渲染')
        return <div className={styles.count}>{this.props.formatDate()}</div>
      }
    }
    
    

    同样多次点击更新视图时,由于传给纯组件 MyDate 的 formatDateCallback 函数是被缓存起来的,纯组件没有进行重新渲染

    React Hooks系列(一):常用Api的使用方法

    useMemo、useCallback 小结

    1. useMemo 缓存的是一个值,第一个参数(回调函数)的返回值就是这个值,第二个参数是个数组里面的每一项变化都会重新执行回调进行缓存
    2. useCallBack 缓存的是一个函数,第一个参数就是要缓存的函数,第二个参数是个数组,里面的每一项变化都会重新缓存这个函数
    3. useCallback(fn, deps) 相当于 useMemo(() => fn, deps)
    4. 当我们代码中出现了逻辑复杂的计算,每一次执行成本都很昂贵,我们完全可以使用 useMemo 缓存下来计算结果,当所依赖的数据项变化时再重新计算,减少重复计算
    5. 这里建议不要频繁使用 useMemo、useCallBack ,可以在功能完成后使用它们进行优化,因为他们本身都是基于闭包实现的,同样占用性能, useState、useEffect、useRef 同样都有缓存的能力,在逻辑实现使用他们足够,只有当你真正需要 useMemo、useCallback 他们时,再斟酌使用

    下载网 » React Hooks系列(一):常用Api的使用方法

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元