React Hooks的一些理解

useState

一个可以根据key 动态setFormData的例子
function Component() {
  const [person, setPerson] = useState({ name: 'puxiao', age: 34 });
  const valueChange = (key, e) => {
    person[key] = e.target.value
    setPerson({...person}) 
  }

  return <div>
    <input type='text' value={person.name} onChange={e => valueChange('name', e)} />
    <input type='number' value={person.age} onChange={e => valueChange('age', e)} />
    {JSON.stringify(person)}
  </div>
}
useState中修改对象如何操作
import React, { useState } from 'react';

function Component() {

  const [str, setStr] = useState('');
  const [arr, setArr] = useState(['react', 'Koa']);

  const inputChangeHandler = (eve) => {
    setStr(eve.target.value);
  }

  const addHeadHandler = (eve) => {
    setArr([str,...arr]);//添加至头
    setStr('');
  }

  const addEndHandler = (eve) => {
    setArr([...arr, str]);//添加至尾
    setStr('');
  }

  const delHeadHandler = (eve) => {
    let new_arr = [...arr];
    new_arr.shift();//从头删除1项目
    setArr(new_arr);
  }

  const delEndHandler = (eve) => {
    let new_arr = [...arr];
    new_arr.pop();//从尾删除1项目
    setArr(new_arr);
  }

  const delByIndex = (eve) => {
    let index = eve.target.attributes.index.value;
    let new_arr = [...arr];
    new_arr.splice(index,1);//删除当前项
    setArr(new_arr);
  }

  return <div>
    <input type='text' value={str} onChange={inputChangeHandler} />
    <button onClick={addHeadHandler} >添加至头</button>
    <button onClick={addEndHandler} >添加至尾</button>
    <button onClick={delHeadHandler} >从头删除1</button>
    <button onClick={delEndHandler} >从尾删除1</button>
    <ul>
        {arr.map(
            (item, index) => {
                return <li key={`item${index}`}>学习{index} -  {item}
                    <span index={index} onClick={delByIndex} style={{ cursor: 'pointer' }}>删除</span>
                </li>
            }
        )}
    </ul>
  </div>
}

export default Component;
useState中修改深层次(大于三级嵌套的数组)对象如何操作

useCallback

不能做的优化
import React, { useState, useCallback } from 'react'
export default function UseCallbackDemoOne() {
  const [count,setCount] = useState(0)
  
  const  increament1 = () => {
    console.log('执行increament1')
    setCount(count + 1)
  }
 
  const  increament2 = useCallback(()=>{
    console.log('执行increament2')
    setCount(count + 1)
  },[])

  return (
    <div>
      <h2>{count}</h2>
      <button onClick={increament1}>+1</button>
      <button onClick={increament2}>+1</button>
    </div>
  )
}

如图increament1所示,不管怎么修改count,不需要加useCallback也能获取到最新的count。图二中useCallback会返回一个函数的memoized(记忆的)值。如果不加依赖,会一直拿到原本得值。
如果使用useCallback也没法优化性能,不会重新渲染吗?
答:还是会重新渲染,所以usecallback在这种单个组件不能优化性能。

可以优化的情况
import React, { useState, useCallback, memo } from 'react'

const ChildComp =
  memo( // 这里的memo作用是减少子组件渲染。(前提是传递过来的是普通的props)
    ({
      changeCount,
      count }) => {
      console.log('重渲染');

      return <div>子组件
        <button onClick={changeCount}>改变子组件的count</button>
        <div>子组件的数据 count: {count}</div>
      </div>
    }
  )

export default function UseCallbackDemoOne() {
  const [count, setCount] = useState(0) // 有关的数据
  const [name, setName] = useState(1)   // 无关的数据

  // 这里的useCallback作用是减少子组件渲染 (前提是将要传递给子组件的一个函数)
  // 有没有useCallback的区别:usecallback如果不加,子组件每次都会渲染,而且前提是子组件也得加上memo。
  const changeCount = useCallback(() => {
    setCount(value => value + 1)
  }, [])

  return <div>
    父组件
    count: {count}
    name: {name}

    <button onClick={() => setName(name + 1)}>name + 1</button>
    <hr />
    子组件
    <ChildComp
      changeCount={changeCount}
      count={count} />
    <ChildComp
      changeCount={changeCount}
      count={count} />
  </div>
}

如果传递给子组件的事件去修改了父组件的数据,并且使用memo和useCallback,并不会触发无关的渲染。可以优化性能。如下所示:


import React, { useState, useCallback, memo } from 'react'

// 这个memo很重要,可以减少子组件不必要的渲染。
const ChildComp = memo(({ setCountFun, setNameFun }) => {
  console.log('子组件重渲染');

  return <div>子组件

    <button onClick={setCountFun}>setCountFun</button>
  </div>
})

export default function UseCallbackDemoOne() {
  const [count, setCount] = useState(0)
  const [name, setName] = useState(0)

  const setCountFun = useCallback(() => {
    setCount(count + 1)
  }, [count])

  const setNameFun = useCallback(() => {
    setName(count + 1)
  }, [])

  return (
    <div>
      <h2>{count}</h2>
      <h2>{name}</h2>
      <button onClick={() => setCount(count + 1)}>count + 1</button>
      <button onClick={() => setName(name + 1)}>name + 1</button>
      <button onClick={() => setCountFun()}>setCountFun</button>
      {/* {console.log('重新渲染')} */}

      <ChildComp setCountFun={setCountFun} />
      <ChildComp setNameFun={setNameFun} />
    </div>
  )
}

useMemo


import React, { useState, useCallback, memo, useMemo } from 'react'

// 这个memo很重要,可以减少子组件不必要的渲染。
const ChildComp = memo(({ obj }) => {
  console.log('子组件重渲染');

  return <div>子组件

    {/* <button onClick={setCountFun}>setCountFun</button> */}
  </div>
})

export default function UseCallbackDemoOne() {
  const [count, setCount] = useState(0)
  const [name, setName] = useState(0)
  const [obj, setObj] = useState({
    age: 20,
    name: 'glack'
  })

  const setCountFun = useCallback(() => {
    setCount(count + 1)
  }, [count])

  const setNameFun = useCallback(() => {
    setName(count + 1)
  }, [])

  return (
    <div>
      <h2>{count}</h2>
      <h2>{name}</h2>
      <button onClick={() => setCount(count + 1)}>count + 1</button>
      <button onClick={() => setName(name + 1)}>name + 1</button>

      <ChildComp obj={useMemo(() => ({obj, test: 'tetst'}), [])}/>
	// <ChildComp obj={{obj, test: 'tetst'}}/> 如果传递的属性是一个对象,并且不加useMomo会导致子组件多出无用的渲染,修改count,但是子组件没有依赖count,也会造成渲染。
    </div>
  )
}

useMemo和useCallback类似,一个是可以缓存函数(useCallback),一个是可以缓存属性(useMemo)。在值有所依赖的项,并且是对象和数组等值的时候而使用useMemo(当返回的是原始数据类型如字符串、数字、布尔值,就不要使用useMemo了)。不要盲目使用这些hooks。

补充说明:
1、useMemo并不需要子组件必须使用React.memo。
2、“不必要的函数计算”中的函数计算必须是有一定复杂度的,例如需要1000个for循环才能计算出的某个值。如果计算量本身很简单,例如1+2,那完全没有必要使用useMemo,就直接每次重新计算一遍也无所谓。

不需要子组件必须使用React.memo,例子:
import { useMemo, useState } from "react"

export default function IndexPage() {

  const [name, setName] = useState('glack')
  const [age, setAge] = useState(10)

  const desc = useMemo(() => {
    console.log('desc重新执行');
    return {
      age: 1,
      desc: '我是glack,年龄是' + age
    }
  }, [age])

  // const desc = () => {
  // 如果如此使用,修改name将会执行一帧。
  //   for 1000 .

  //   console.log('desc重新执行');
  //   return {
  //     age: 1,
  //     desc: '我是glack,年龄是' + age
  //   }
  // }



  return (<div>
    name: {name} <br />
    age:  {age} <br />
    desc: {JSON.stringify(desc)} <br />
    {/* desc: {JSON.stringify(desc())} <br /> */}
    <button onClick={() => setName(name + 1)}>name + 1</button> <br />
    <button onClick={() => setAge(age + 1)}>age + 1</button> <br />


  </div>)
}

useContext简单使用

import GlobalContext from './global-context'; //引入共享数据对象

function Component(){
  const global = useContext(GlobalContext); //在函数组件中声明一个变量来代表该共享数据对象的value值

  //若想获取共享数据对象中的属性xxx的值,直接使用global.xxx即可
  return <div>
    {global.xxx}
  </div>
}

// global-context.js
import React from 'react';

const GlobalContext = React.createContext({
    name: 'glack',
    age: 20
}); 
//请注意,这里还可以给React.createContext()传入一个默认值
//例如:const GlobalContext = React.createContext({name:'Yang',age:18})
//假如<GlobalContext.Provider>中没有设置value的值,就会使用上面定义的默认值
export default GlobalContext;
父组件传递context给子组件
import React,{ useContext } from 'react'

const UserContext = React.createContext();
const NewsContext = React.createContext();

function AppComponent() {
  return (
    <UserContext.Provider value={{name:'puxiao'}}>
        <NewsContext.Provider value={{title:'Hello React Hook.'}}>
            <ChildComponent />
        </NewsContext.Provider>
    </UserContext.Provider>
  )
}

function ChildComponent(){
  const user = useContext(UserContext);
  const news = useContext(NewsContext);
  return <div>
    {user.name} - {news.title}
  </div>
}

export default AppComponent;

主要用到了XxxContext.Provider这个api

为什么不使用Redux?

在Hook出现以前,React主要负责视图层的渲染,并不负责组件数据状态管理,所以才有了第三方Redux模块,专门来负责React的数据管理。
但是自从有了Hook后,使用React Hook 进行函数组件开发,实现数据状态管理变得切实可行。只要根据实际项目需求,使用useContext以及下一章节要学习的useReducer,一定程度上是可以满足常见需求的。
毕竟使用Redux会增大项目复杂度,此外还要花费学习Redux成本。
具体需求具体分析,不必过分追求Redux。

useReducer

在React 16.8版本以前,通常需要使用第三方Redux来管理React的公共数据,但自从 React Hook 概念出现以后,可以使用 useContext + useReducer 轻松实现 Redux 相似功能。这一部分会在 “useReducer高级用法” 中做详细讲解。

补充说明:
1、在React源码中,实际上useState就是由useReducer实现的,所以useReducer准确来说是useState的原始版。
2、无论哪一个Hook函数,本质上都是通过事件驱动来实现视图层更新的。

// 一个更改计数器的例子

import React, { useReducer } from 'react';

// 这里的reducer和redux的reducer写法一致。
function reducer(state,action){
  switch(action.type){
    case 'add':
      return state + action.param;
    case 'sub':
      return state - action.param;
    case 'mul':
      return state * action.param;
    default:
      console.log('what?');
      return state;
  }
}

function getRandom(){
  return Math.floor(Math.random()*10);
}

function CountComponent() {
  const [count, dispatch] = useReducer(reducer,0);

  return <div>
    {count}
    <button onClick={() => {dispatch({type:'add',param:getRandom()})}} >add</button>
    <button onClick={() => {dispatch({type:'sub',param:getRandom()})}} >sub</button>
    <button onClick={() => {dispatch({type:'mul',param:getRandom()})}} >mul</button>
  </div>;
}

export default CountComponent;
使用useReducer来管理复杂类型的数据
const initralData = {loading: true,result: '',error: false};

const reducer = (state, action) => {
  switch (action.type) {
    case 'succes':
        return {loading:false,result:action.res,error:false}
    case 'error':
        return {loading:false,error:true}
  }
}

function Component() {
  const [state, dispatch] = useReducer(reducer, initralData);

  {
      //ajax请求成功
      dispatch({type:'succes',res:'You have a good news!'});

      //ajax请求错误
      dispatch({type:'error'});
  }

  return <div>
    {state.loading ? 'loading...' : state.result}
    {state.error ? 'wrong!' : null}
  </div>
}
使用 useContext + useReducer 轻松实现 Redux 相似功能

共享对象 代码如下:

import React from 'react';
const CountContext = React.createContext();
export default CountContext;

父组件代码

import React, { useReducer } from 'react';
import CountContext from './CountContext';
import ComponentA from './ComponentA';
import ComponentB from './ComponentB';
import ComponentC from './ComponentC';

const initialCount = 0; //定义count的默认值

//修改count事件处理函数,根据修改参数进行处理
function reducer(state, action) {
//注意这里先判断事件类型,然后结合携带的参数param 来最终修改count
switch (action.type) {
    case 'add':
        return state + action.param;
    case 'sub':
        return state - action.param;
    case 'mul':
        return state * action.param;
    case 'reset':
        return initialCount;
    default:
        console.log('what?');
        return state;
}
}

function ParentComponent() {
  //定义全局变量count,以及负责抛出修改事件的dispatch
  const [count, dispatch] = useReducer(reducer, initialCount);

  //请注意:value={{count,dispatch} 是整个代码的核心,把将count、dispatch暴露给所有子组件
  return <CountContext.Provider value={{count,dispatch}}>
    <div>
        ParentComponent - count={count}
        <ComponentA />
        <ComponentB />
        <ComponentC />
    </div>
  </CountContext.Provider>
}

export default ParentComponent;

子组件代码

import React,{ useState, useContext } from 'react';
import CountContext from './CountContext';

function CopmpoentA() {
  const [param,setParam] = useState(1);
  //引入全局共享对象,获取全局变量count,以及修改count对应的dispatch
  const countContext = useContext(CountContext);

  const inputChangeHandler = (eve) => {
    setParam(eve.target.value);
  }

  const doHandler = () => {
    //若想修改全局count,先获取count对应的修改抛出事件对象dispatch,然后通过dispatch将修改内容抛出
    //抛出的修改内容为:{type:'add',param:xxx},即告诉count的修改事件处理函数,本次修改的类型为add,参数是param
    //这里的add和param完全是根据自己实际需求自己定义的
    countContext.dispatch({type:'add',param:Number(param)});
  }

  const resetHandler = () => {
    countContext.dispatch({type:'reset'});
  }

  return <div>
        ComponentA - count={countContext.count}
        <input type='number' value={param} onChange={inputChangeHandler} />
        <button onClick={doHandler}>add {param}</button>
        <button onClick={resetHandler}>reset</button>
    </div>
}

export default CopmpoentA;

useRef

基础使用
import React,{useEffect,useRef} from 'react'

function Component() {
  //先定义一个inputRef引用变量,用于“勾住”挂载网页后的输入框
  const inputRef = useRef(null);

  useEffect(() => {
    //inputRef.current就是挂载到网页后的那个输入框,一个真实DOM,因此可以调用html中的方法focus()
    inputRef.current.focus();
  },[]);

  return <div>
      {/* 通过 ref 属性将 inputRef与该输入框进行“挂钩” */}
      <input type='text' ref={inputRef} />
    </div>
}
export default Component
useRef 配合定时器使用
import React,{useState,useEffect,useRef} from 'react'

function Component() {
  const [count,setCount] =  useState(0);
  const timerRef = useRef(null);//先定义一个timerRef引用变量,用于“勾住”useEffect中通过setIntervale创建的计时器

  useEffect(() => {
    //将timerRef.current与setIntervale创建的计时器进行“挂钩”
    timerRef.current = setInterval(() => {
        setCount((prevData) => { return prevData +1});
    }, 1000);
    return () => {
        //通过timerRef.current,清除掉计时器
        clearInterval(timerRef.current);
    }
  },[]);

  const clickHandler = () => {
    //通过timerRef.current,清除掉计时器
    clearInterval(timerRef.current);
  };

  return (
    <div>
        {count}
        <button onClick={clickHandler} >stop</button>
    </div>
  )
}

export default Component

两种实现方式对比:

1、两种实现方式最主要的差异地方在于 如何创建组件内对计时器的引用。
2、两种创建引用的方式,分别是:用useState创建的timer、用useRef创建的timerRef
3、在使用setInterval时,相对来说timerRef.current更加好用简单,结构清晰,不需要像 setTimer那样需要再多1层包裹。
4、timer更像是一种react对计时器的映射,而timerRef直接就是真实DOM中计时器的引用,timerRef能够调用更多的原生html中的JS方法和属性。

结论:
1、如果需要对渲染后的DOM节点进行操作,必须使用useRef。
2、如果需要对渲染后才会存在的变量对象进行某些操作,建议使用useRef。

第3遍强调:useRef只适合“勾住”小写开头的类似原生标签的组件。如果是自定义的react组件(自定义的组件必须大写字母开头),那么是无法使用useRef的。

父组件调用子组件中的函数

ParentComponent

import { useRef } from "react";
import ChildComponent from "./child";

const ParentComponent = () => {
  const childFunRef = useRef();
  const handleOnClick = () => {
    if (childFunRef.current) {
      childFunRef.current.doSomething();
    }
  };
  return (
    <div>
      <ChildComponent funRef={childFunRef} />
      <button onClick={handleOnClick}>执行子项的doSomething()</button>
    </div>
  );
};

export default ParentComponent;

ChildComponent

import { useEffect, useState } from "react";

const ChildComponent = ({ funRef }) => {
  const [num, setNum] = useState(0);
  useEffect(() => {
    const doSomething = () => {
      setNum(Math.floor(Math.random() * 100));
    };
    funRef.current = { doSomething }; //在子组件中修改父组件中定义的childFunRef的值
  }, [funRef]);
  return <div>{num}</div>;
};

export default ChildComponent;

附件:一个大佬写的总结

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值