react hooks初学者入门总结

react hooks初学者入门总结

近期接触react hooks,在这里总结一下学习之路。总的来说,react hooks的学习就是几个方法的学习,废话不多说,起飞!

为什么要用hooks

  • 状态逻辑难以复用: 业务变得复杂之后,组件之间共享状态变得频繁,组件复用和状态逻辑管理就变得十分复杂。使用 redux 也会加大项目的复杂度和体积。
  • 组成复杂难以维护: 复杂的组件中有各种难以管理的状态和副作用,在同一个生命周期中你可能会因为不同情况写出各种不相关的逻辑,但实际上我们通常希望一个函数只做一件事情。
  • 类的 this 指向性问题: 我们用 class 来创建 react 组件时,为了保证 this 的指向正确,我们要经常写这样的代码:const that = this,或者是this.handleClick = this.handleClick.bind(this)>;一旦 this 使用错误,各种 bug 就随之而来。

为了解决这些麻烦,hooks 允许我们使用简单的特殊函数实现 class 的各种功能。

useState

设置和改变 state,代替原来的 state 和 setState

在 React 组件中,我们经常要使用 state 来进行数据的实时响应,根据 state 的变化重新渲染组件更新视图。

因为纯函数不能有状态,在 hooks 中,useState就是一个用于为函数组件引入状态(state)的状态钩子。

在类组件中我们更新状态是这样的

 // 添加todo
    _addTodo(todo){
        // 1)插入todo
        let { todos } = this.state;
        todos.push(todo)
        // 2)更新状态机
        this.setState({
            todos
        })
     }

​ 现在在函数组件中我们更新状态是使用useState

function App() {
  const [count,setState] = useState(0);
  return (
    <div className="App">
      <p>你点击了{count}</p>
      <button onClick={()=>{setState(count+1)}}>点击</button>
    </div>
  );
}

export default App;
// count 声明的变量
// setCount 设用来更新变量的函数
// 0 初始值 初始值可以是数字,字符串等,也可以是对象或者数组。
// 多个状态声明不能出现在条件判断语句中

useState 的唯一参数是状态初始值(initial state),它返回了一个数组,这个数组的第[0]项是当前当前的状态值,第[1]项是可以改变状态值的方法函数。

可以理解为类组件中的setState,相同的是都是异步的,不同的是类中的 setState 是合并,而函数组件中的 setState 是替换。这个初始 state 参数只有在第一次渲染时会被用到。

useEffect

引入具有副作用的操作,类比原来的生命周期

之前在类组件中有没有被生命周期函数折磨到?很多的生命周期函数,而且还很长,每一个什么功能什么时候可以使用,是不是很困扰呢?
在hooks里面就有一个很厉害的hook替代了三个常用的生命周期钩子,componentDidMount,componentDidUpdate和componentWillUnmount。我们写的有状态组件,通常会产生很多的副作用(side effect),比如发起ajax请求获取数据,添加一些监听的注册和取消注册,手动修改dom等等。hooks是可以反复多次使用,相互独立的。所以我们给每一个副作用一个单独的useEffect钩子。这样一来,这些副作用不再一股脑堆在生命周期钩子里,代码变得更加清晰。

useEffect(()=>{
    // some code
}, [])

第二个参数是[] 类似于生命周期函数的 componentDidMount。此时React的 effect 不依赖于 props 或 state 中的任何值,所以它永远都不需要重复执行,只执行一次。可以在这个hook里面发送ajax请求。

useEffect(()=>{
    // some code
}, [count])

第二个参数[count], 只有监听到count发生改变的时候才会更新

useEffect(()=>{
    return () => { 
        // some code
    }
})
// 返回一个函数实现解绑

useCallback

类似 useMemo,useMemo 优化传值,usecallback 优化传入的方法

第二个参数传入一个数组,数组中的每一项一旦值或者引用发生改变,useCallback 就会重新返回一个新的记忆函数提供给后面进行渲染,可以有效避免不必要的 vDOM 渲染。

function App() {
  const handleClick = useCallback(() => {
    console.log('Click happened')
  }, []); // 空数组代表无论什么情况下该函数都不会发生改变
  return <SomeComponent onClick={handleClick}>Click Me</SomeComponent>;
}

useMemo

可根据状态变化控制方法执行,优化无用渲染,提高性能

useMemo 与 useCallback 类似,都是有着缓存的作用。useMemo 是缓存值的,useCallback 是缓存函数的。

const data = useMemo(()=>{
        return {
            name
        }
    },[name])

先根据[name]里面的name值判断一下,因为useMemo 作为一个有着暂存能力的,暂存了上一次的name结果。结果一对比上一次的name,我们发现name值居然没有改变!那么这次data就不重新赋值成新的对象了!memo是浅比较,意思是,对象只比较内存地址,只要你内存地址没变,管你对象里面的值千变万化都不会触发render。

useCallback 不会执行第一个参数函数,而是将它返回给你,而 useMemo 会执行第一个函数并且将函数执行结果返回给你。
useCallback 常用记忆事件函数,生成记忆后的事件函数并传递给子组件使用。useMemo 更适合经过函数计算得到一个确定的值。

useRef

返回一个可变的 ref 对象

useRef 返回一个可变的 ref 对象,其 .current 属性被初始化为传递的参数(initialValue),是一个通用容器,其 current 属性是可变的,可以保存任何值(可以是元素、对象、基本类型、甚至函数),类似于类组件用 React.createRef,。

import React, { useRef, useState, useEffect } from 'react'; 
function StudyuseRef(){
    
    // 使用 useRef 创建 inputEl 
    const inputEl = useRef(null);
    const [text, updateText] = useState('');
    // 使用 useRef 创建 textRef 
    const textRef = useRef();
    useEffect(() => {
        // 将 text 值存入 textRef.current 中
        textRef.current = text;
        console.log('textRef.current:', textRef.current);
    });
    const onButtonClick = () => {
        inputEl.current.value = "Hello, useRef";
    };
    return (
        <>
            {/* 保存 input 的 ref 到 inputEl */}
            <input ref={ inputEl } type="text" />
            <button onClick={ onButtonClick }>在 input 上展示文字</button>
            <br />
            <input value={text} onChange={e => updateText(e.target.value)} />
        </>
    );
}

export default StudyuseRef;

点击 在 input 上展示文字 按钮,就可以看到第一个 input 上出现 Hello, useRef;在第二个 input 中输入内容,可以看到控制台打印出对应的内容。

useContext

上下文爷孙组件及更深层组件传值

状态全局化并能实现统一管理,便于子父组件状态共享。

import React,{ useState ,createContext, useContext} from "react";
// 在它们的父组件上使用React的Context API,在组件外部建立一个Context。
const CountContext = createContext();
//子组件的代码
function Counter(){
  // useContext()用来引入Context对象,从中获取count属性。
  let count = useContext(CountContext)
  return(
    <h2>{count}</h2>
  )
}

function StudyuseContext() {
  const [count,setState] = useState(0); 
  
  return (
    <div>
      <p>你点击了{count}</p>
      <button onClick={()=>{setState(count+1)}}>点击</button>
			//组件封装代码 CountContext.Provider提供了一个Context对象,这个对象是可以被子组件共享的。
      <CountContext.Provider value={count}>
        <Counter></Counter>
      </CountContext.Provider>
    </div>
  );
}

export default StudyuseContext;

useReducer

代替原来 redux 里的 reducer,方便管理状态逻辑

​ 提供类似 Redux 的功能,引入 useReducer 后,useReducer 接受一个 reducer 函数作为参数,reducer 接受两个参数一个是 state 另一个是 action 。然后返回一个状态 count 和 dispath,count 是返回状态中的值,而 dispatch 是一个可以发布事件来更新 state 的。通过dispatch action更新逻辑复杂状态,控制业务逻辑。

import React,{ useReducer} from "react";

function StudyuseReducer() {
  const [count , dispatch]=useReducer((state,action)=>{
    switch(action){
      case 'add':
        return state+1
      case 'sub':
        return state-1
      default:
        return state
    }
  },0)
 
  return (
    <div>
      <p>现在的分数是{count}</p>
      <button onClick={()=>{dispatch('add')}}></button>
      <button onClick={()=>{dispatch('sub')}}></button>
    </div>
  );
}

export default StudyuseReducer;

对比 useState 可知,看起来我们的代码好像变得复杂了,但实际应用到复杂的项目环境中,将状态管理和代码逻辑放到一起管理,使我们的代码具有更好的可读性、可维护性和可预测性。

优化:延迟初始化
还可以懒惰地创建初始状态。为此,您可以将init函数作为第三个参数传递。初始状态将设置为 init(initialArg)。

useContext 与 useReducer 实现redux的状态管理和状态共享

实现状态全局化并能统一管理,统一个事件的派发。实现一个点击按钮改变字体颜色的小案例:

// 字体组件
import React ,{useContext}from "react";
import {ColorContext} from "./colors"

 function ShowArea(){
   // 获取color
    const {color} = useContext(ColorContext)
    return (
        <div style={{color:color}}>
            字体颜色为{color}
        </div>
    )
}
export default ShowArea;
//按钮组件
import React ,{useContext,}from "react";

import {ColorContext,UPDATE_COLOR} from './colors';

function Buttons(){
  // 获取共享的dispatch
    const {dispatch} = useContext(ColorContext)
    return(
        <div>
      			{/* 使用dispatch派发一个action */}
            <button onClick={()=>{dispatch({type:UPDATE_COLOR,color:'red'})}}>红色</button>
            <button onClick={()=>{dispatch({type:UPDATE_COLOR,color:'yellow'})}}>黄色</button>
        </div>
    )
}
export default Buttons;
//color组件 状态管理
import React ,{ createContext , useReducer} from "react";

export const ColorContext = createContext({})

export const UPDATE_COLOR = "UPDATE_COLOR"
// 定义reducer
const reducer =(state,action)=>{
    switch(action.type){
        case UPDATE_COLOR:
            return action.color
        default:
             return state
    }
}

// 颜色共享
export const Color = props =>{
 	 // 使用reducer
    const [color,dispatch]= useReducer(reducer,'blue')
    return(
      {/* 将color和dispatch共享出去 */}
        <ColorContext.Provider value={{color,dispatch}}>
            {props.children}
        </ColorContext.Provider>
    )
}
//父组件
import React from "react";
import Buttons from "./Buttons";
import { Color } from "./colors";
import ShowArea from "./showArea";

function Example(){
    return (
        <div>
            <Color>
                <ShowArea></ShowArea>
                <Buttons></Buttons>
            </Color>
            
        </div>
    )
}
export default Example;

useSelector

作用:共享状态,从Redux的store中提取数据(state)

通过createStore将state存入store中,再将state暴露出来给子组件,通过store在子父组件中共享状态。

const num=useSelector(state=>state.num)

useDispatch

作用:共享状态,返回Redux的store中对dispatch的引用,可执行redux中的方法

const dispatch=useDispatch()

useImperativeHandle

可以让你在使用 ref 时自定义暴露给父组件的实例值

useImperativeHandle 的第一个参数是定义 current 对象的 ref,第二个参数是一个函数,返回值是一个对象,即这个 ref 的 current 对象,这样可以像上面的案例一样,通过自定义父组件的 ref 来使用子组件 ref 的某些方法。

//子组件
useImperativeHandle(editorRef, () => ({
        initValue: value => {
            seteEitorState(BraftEditor.createEditorState(value));
        }
    }));


// 父组件
export default () => {
    const editorRef = useRef();
            const editorBool = _.includes([
                COMP_TYPE.EDITOR,
                COMP_TYPE.TEXTEDITOR,
                COMP_TYPE.DIALOGBTN,
                COMP_TYPE.DESCMODULE,
                COMP_TYPE.DIALOGIMG
            ], compType);
            // console.log(editorRef.current);
            if (editorRef.current && editorBool) {
                editorRef.current.initValue(editor.editorText);
            }
        }
    }, [moduleIndex, compIndex]);
    return (
        <div className="prop-config-text">
      		<Form.Item name="editor" label="文本">
             <BraftEditorText editorRef={editorRef} />
          </Form.Item>
        </div>
    );
};



自定义hooks

先说一下,其实官方的hook已经很多很全了,状态我们可以 useState ,复杂多状态我们可以用useReducer ,共享和传递状态可以使用 useContext ,引用组件、引用状态可以 useRef ,组件render完成之后的操作通过 useEffect 完成…还有其他几个hook,那么我们为什么还需要自定义hooks呢?

其实,自定义hook也是基于官方的hook进行组合,逻辑复用,业务代码解耦抽象后进一步提炼出来的具备一定功能的函数。它应当具有一定条件下的的通用性,可移植性。
目前的hook可能并不十分契合我们的需求,我们需要进行二次加工,成为我们的业务hook, 官方推荐自定义hook命名以use开头。

hooks使用以及编写规范

  1. 不要从常规 JavaScript 函数调用 Hooks;
  2. 不要在循环,条件或嵌套函数中调用 Hooks;
  3. 必须在组件的顶层调用 Hooks;
  4. 可以从 React 功能组件调用 Hooks;
  5. 可以从自定义 Hooks 中调用 Hooks;
  6. 自定义 Hooks 必须使用 use 开头,这是一种约定;
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

秃八哥

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

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

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

打赏作者

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

抵扣说明:

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

余额充值