React Hook的使用

前言

React 16.8之前,组件使用state,你需要创建类组件,随着React 16.8发布,新增了hook新特性后,我们可以在函数中使用state以及其他特性,并解决了使用class带来的一些问题,所以我们有必要学习hook,这也是react官方所推荐的,下面我通过自己编写的demo,让大家更快速、简单明了的学习hook新特性

一、React Hook是什么?

Hook 是 React 16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。

Hook的特点

①完全可选的 你无需重写任何已有代码就可以在一些组件中尝试 Hook。但是如果你不想,你不必现在就去学习或使用 Hook。
②100% 向后兼容的 Hook 不包含任何破坏性改动。
③现在可用 Hook 已发布于 v16.8.0。
④没有计划从 React 中移除 class
⑤Hook 不会影响你对 React 概念的理解 恰恰相反,Hook 为已知的 React 概念提供了更直接的 API:props, state,context,refs 以及生命周期。

二、React目前提供的Hook

hook介绍
useState代替原来的state和setState,设置和改变state
useEffect代替原来的生命周期,componentDidMount,componentDidUpdate 和 componentWillUnmount 的合并版
useLayoutEffect同useEffect功能,实现同步调用
useCallbackuseMemo优化传值,usecallback优化传的方法,是否更新
useMemo可根据状态变化控制方法执行,优化不必要的渲染
useRef更简洁的使用ref
useContext组件间传值,更深级组件传值
useReducer代替原来redux里的reducer,配合useContext使用
useDebugValue在 React 开发者工具中显示自定义 hook 的标签,调试使用
useImperativeHandle使用 ref 时自定义暴露给父组件的实例值

三、Hook的使用

1、useState
import React, { useState } from 'react'

const margin10 = {
  margin: '10px 0 20px',
}

function UseState() {
  // 声明一个叫 “count” 的 state 变量。
  const [count, setCount] = useState(0)
  const [fruit, setFruit] = useState('banana')
  const [todos, setTodos] = useState([{ text: 'Learn Hooks' }])

  // 新增列表项
  function addTodos(item) {
    // 得返回一个新数组
    return [...todos, item]
  }
  return (
    <div>
      {/* 示例一,计数器 */}
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>Click me</button>
      <hr style={margin10} />

      {/* 实例二,添加字符串 */}
      <p>{fruit}</p>
      <button onClick={() => setFruit(fruit + 'apple')}>Click me2</button>
      <hr style={margin10} />

      {/* 实例三,添加数组 */}
      <button
        // onClick={() => setTodos([...todos, { text: 'aliang' + todos.length }])}
        onClick={() => setTodos(addTodos({ text: 'aliang' + todos.length }))}
      >
        Click me3
      </button>
      {todos.map((item, index) => (
        <p key={index}>{item.text}</p>
      ))}
    </div>
  )
}

export default UseState
2、useEffect & useLayoutEffect
import React, { useState, useEffect, useLayoutEffect } from 'react'

//箭头函数的写法,改变状态
const UseEffect = (props) => {
  //创建了一个叫hook的变量,sethook方法可以改变这个变量,初始值为‘react hook 是真的好用啊’
  const [hook, sethook] = useState('react hook 是真的好用啊')
  const [name] = useState('aliang')
  return (
    <header className="UseEffect-header">
      <h3>UseEffect</h3>
      <Child hook={hook} name={name} />
      {/**上面的变量和下面方法也是可以直接使用的 */}
      <button
        onClick={() =>
          sethook('我改变了react hook 的值' + new Date().getTime())
        }
      >
        改变hook
      </button>
    </header>
  )
}

const Child = (props) => {
  const [newhook, setnewhook] = useState(props.hook)
  //这样写可以代替以前的componentDidMount,第二个参数为空数组,表示该useEffect只执行一次
  useEffect(() => {
    console.log('first componentDidMount')
  }, [])

  useEffect(() => {
    console.log('DidMount && DidUpdate', newhook)
  })

  //第二个参数,数组里是hook,当hook变化时,useEffect会触发,当hook变化时,先销毁再执行第一个函数。
  useEffect(() => {
    setnewhook(props.hook + '222222222')
    console.log('useEffect')
    return () => {
      console.log('componentWillUnmount ')
    }
  }, [props.hook])

  //useLayoutEffect 强制useeffect的执行为同步,并且先执行useLayoutEffect内部的函数
  useLayoutEffect(() => {
    console.log('useLayoutEffect')
    return () => {
      console.log('useLayoutEffect componentWillUnmount')
    }
  }, [props.hook])

  return (
    <div>
      <p>{props.name}</p>
      {newhook}
    </div>
  )
}

export default UseEffect
3、useMemo & useCallback
import React, { useState, useMemo } from 'react'

const Child = ({ age, name, children }) => {
  const [childName, setChildName] = useState('儿子')
  // 在不用useMemo做处理的时候,只要父组件状态改变了,子组件都会渲染一次,用了useMemo可以监听某个状态name,当name变化时候执行useMemo里第一个函数
  console.log(age, name, children, '11111111')
  function namechange() {
    console.log(age, name, children, '22222222')
    setChildName(childName + 1)
    return name + 'change'
  }
  // react 官网虽说useCallback与useMemo的功能差不多,但不知道版本问题还怎么回是,这个方法目前还不能用
  /* const memoizedCallback = React.useCallback(() => {
    console.log('useCallback', name)
    return 'count=' + name
  }, [name])
  console.log(memoizedCallback, 'memoizedCallback') */
  //useMemo有两个参数,和useEffect一样,第一个参数是函数,第二个参数是个数组,用来监听某个状态不变化
  const changedname = useMemo(() => namechange(), [name])
  return (
    <div style={{ border: '1px solid' }}>
      <p>children:{children}</p>
      <p>name:{name}</p>
      <p>changed:{changedname}</p>
      <p>age:{age}</p>
    </div>
  )
}

const UseMemo = () => {
  //useState 设置名字和年龄,并用2两个按钮改变他们,传给Child组件
  const [name, setname] = useState('aliang')
  const [age, setage] = useState(18)
  return (
    <div>
      <button
        onClick={() => {
          setname('aliang' + new Date().getTime())
        }}
      >
        改名字
      </button>
      <button
        onClick={() => {
          setage('年龄' + new Date().getTime())
        }}
      >
        改年龄
      </button>
      <p>
        UseMemo {name}{age}
      </p>
      <Child age={age} name={name}>
        {name}的children啊啊啊
      </Child>
    </div>
  )
}

export default UseMemo
4、useRef
import React, { useState, useRef } from 'react'

const UseRef = () => {
  //这里useState绑定个input,关联一个状态name
  const [name, setname] = useState('aliang')
  const refvalue = useRef(null) // 先创建一个空的useRef
  function addRef() {
    refvalue.current.value = name //点击按钮时候给这个ref赋值
    // refvalue.current = name //这样写时,即使ref没有绑定在dom上,值依然会存在创建的ref上,并且可以使用它
    console.log(refvalue)
    console.log(refvalue.current.value)
  }
  return (
    <div>
      <input
        defaultValue={name}
        onChange={(e) => {
          setname(e.target.value)
        }}
      />
      <button onClick={addRef}>给下面插入名字</button>
      <p>给我个UseRef名字:</p>
      <input ref={refvalue} />
    </div>
  )
}

export default UseRef

5、useContext
import React, { useState, useContext, createContext } from 'react'

const ContextName = createContext()
//这里为了方便写博客,爷爷孙子组件都写在一个文件里,正常需要在爷爷组件和孙子组件挨个引入创建的Context

const UseContext = () => {
  //这里useState创建一个状态,并按钮控制变化
  const [name, setname] = useState('aliang')
  return (
    <div>
      <h3>UseContext 爷爷</h3>
      <button
        onClick={() => {
          setname('aliang' + new Date().getTime())
        }}
      >
        改变名字
      </button>
      {/**这里跟context用法一样,需要provider向子组件传递value值,value不一定是一个参数 */}
      <ContextName.Provider value={{ name: name, age: 18 }}>
        {/**需要用到变量的子组件一定要写在provider中间,才能实现共享 */}
        <Child />
      </ContextName.Provider>
    </div>
  )
}

const Child = () => {
  //创建一个儿子组件,里面引入孙子组件
  return (
    <div style={{ border: '1px solid' }}>
      Child 儿子
      <ChildChild />
    </div>
  )
}

const ChildChild = () => {
  //创建孙子组件,接受爷爷组件的状态,用useContext,获取到爷爷组件创建的ContextName的value值
  let childname = useContext(ContextName)
  return (
    <div style={{ border: '1px solid' }}>
      ChildChild 孙子
      <p>
        {childname.name}:{childname.age}
      </p>
    </div>
  )
}

export default UseContext
6、useReducer
import React, { useState, useReducer, useContext, createContext } from 'react'

//初始化stroe的类型、初始化值、创建reducer
const ADD_COUNTER = 'ADD_COUNTER'
const initReducer = {
  count: 0,
}
//正常的reducer编写
function reducer(state, action) {
  switch (action.type) {
    case ADD_COUNTER:
      return { ...state, count: state.count + 1 }
    default:
      return state
  }
}

const CountContext = createContext()
//上面这一段,初始化state和reducer创建context,可以单独写一个文件,这里为了方便理解,放一个文件里写了

const UseReducer = () => {
  const [name, setname] = useState('aliang')
  //父组件里使用useReducer,第一个参数是reducer函数,第二个参数是state,返回的是state和dispash
  const [state, dispatch] = useReducer(reducer, initReducer)
  return (
    <div>
      UseReducer
      {/* 在这里通过context,讲reducer和state传递给子组件*/}
      <CountContext.Provider value={{ state, dispatch, name, setname }}>
        <Child />
      </CountContext.Provider>
    </div>
  )
}

const Child = () => {
  //跟正常的接受context一样,接受父组件的值,通过事件等方式触发reducer,实现redux效果
  const { state, dispatch, name, setname } = useContext(CountContext)
  console.log('Child-useContext==>', state)
  function handleclick(count) {
    dispatch({ type: ADD_COUNTER, count: 17 })
    setname(count % 2 === 0 ? 'babybrother' : 'aliang')
  }
  return (
    <div>
      <p>
        {name}今年{state.count}</p>
      <button onClick={() => handleclick(state.count)}>长大了</button>
    </div>
  )
}

export default UseReducer

四、Hook使用禁忌

1、不要在循环,条件或嵌套函数中调用 Hook
2、不要在普通的 JavaScript 函数中调用 Hook

五、总结

①总体写起来比 class 写法舒服,不过对几个基础 hook ,特别是 useState , useEffect 的掌握十分重要
②不需要考虑this指向问题
③更容易复用代码
④清爽的代码风格+代码量更少

代码下载(p-hook分支):https://gitee.com/staraliang/react17-app/tree/p-hook/

希望本文的内容可以帮助到大家!

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序员良仔

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值