[React]React Hooks简单陈列

useState

useState是react自带的一个hook函数,它的作用是用来声明状态变量。

从三个方面来看useState的用法,分别是声明、读取、使用(修改)。

import React, { useState } from 'react';
function Example2(){
    const [ age , setAge ] = useState(18)
    const [ sex , setSex ] = useState('男')
    const [ work , setWork ] = useState('前端程序员')
    return (
        <div>
            <p>今年:{age}岁</p>
            <p>性别:{sex}</p>
            <p>工作是:{work}</p>
        </div>
    )
}
export default Example2;

useEffect

绑定和解绑,用于替代生命周期

useEffect两个注意点

  1. React首次渲染和之后的每次渲染都会调用一遍useEffect函数,而之前我们要用两个生命周期函数分别表示首次渲染(componentDidMonut)和更新导致的重新渲染(componentDidUpdate)。
  2. useEffect中定义的函数的执行不会阻碍浏览器更新视图,也就是说这些函数时异步执行的,而componentDidMonutcomponentDidUpdate中的代码都是同步执行的。
function Example(){
    const [ count , setCount ] = useState(0);

    useEffect(()=>{
        console.log(`useEffect=>You clicked ${count} times`)

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

    return (
        <div>
            <p>You clicked {count} times</p>
            <button onClick={()=>{setCount(count+1)}}>click me</button>

            <Router>
                <ul>
                    <li> <Link to="/">首页</Link> </li>
                    <li><Link to="/list/">列表</Link> </li>
                </ul>
                <Route path="/" exact component={Index} />
                <Route path="/list/" component={List} />
            </Router>
        </div>
    )
}

useContext

import React, { useState , createContext } from 'react';
//===关键代码
const CountContext = createContext()

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

    return (
        <div>
            <p>You clicked {count} times</p>
            <button onClick={()=>{setCount(count+1)}}>click me</button>
            {/*======关键代码 */}
            <CountContext.Provider value={count}>
            </CountContext.Provider>

        </div>
    )
}
export default Example4;

已经有了上下文变量,剩下的就时如何接收了,接收这个直接使用useContext就可以,但是在使用前需要新进行引入useContext(不引入是没办法使用的)。

import React, { useState , createContext , useContext } from 'react';
function Counter(){
    const count = useContext(CountContext)  //一句话就可以得到count
    return (<h2>{count}</h2>)
}

useReducer

useReducer是React hooks提供的函数,可以增强我们的Reducer,实现类似Redux的功能。

import React, { useReducer } from 'react';

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

}

export default ReducerDemo

这段代码是useReducer的最简单实现了,这时候可以在浏览器中实现了计数器的增加减少。

userReducer + userContext 实现redux效果

使用useContextuseReducer是可以实现类似Redux的效果,并且一些简单的个人项目,完全可以用下面的方案代替Redux,这种做法要比Redux简单一些。

demo1:简单加减

父级组件

import React, { useEffect, useReducer } from 'react'
export const sateContext = React.createContext() // 创建并导出context
	
export default function Home (props) {
  const [ state, dispatch ] = useReducer(stateReducer,0) // 创建reducer


  function stateReducer (state, action) { // reducer处理函数
    switch (action.type) {
      case 'add':
        return state + 1;
      case 'sub':
        return state - 1;
      default:
        return state;
    }
	return(
		<sateContext.Provider value={{state,dispatch}}>  // 传入需要共享的数据和reducer,需要注意传参的方式,json格式和直接传入
          <Child />
      </sateContext.Provider>


	)
}

子组件代码 :

import React, { useContext } from 'react'
import {sateContext} from '../Home' // 导入父组件创建的context
import { Button } from 'antd'

export default function Child () {
  const {state,dispatch} = useContext(sateContext)  // 通过usecontext 接收共享数据

  return (
    <div>
      {state}
      <Button onClick={() => {dispatch({type:'add'})}}>加</Button> // 调用dispatch
      <Button onClick={() => {dispatch({type:'sub'})}}>减</Button>
    </div>
  )
}

demo2: 修改颜色

Ⅰ、开始

首先用 create-react-app 创建一个项目

Ⅱ、创建颜色展示组件 ShowArea
import React from 'react'

const ShowArea = (props) => {
  return (
    <div style={{color: "blue"}}>字体颜色展示为blue</div>
  )
}

export default ShowArea
Ⅲ、创建按钮组件 buttons
import React from "react";

const Buttons = props => {
  return (
    <div>
      <button>红色</button>
      <button>黄色</button>
      </div>
  );
};

export default Buttons;

Ⅳ、将 ShowArea 和 Buttons 导入 index.js(等会写出color状态组件后将会改写)
import React from "react";
import ReactDOM from "react-dom";
import ShowArea from './ShowArea'
import Buttons from './Buttons'

function App() {
  return (
    <div className="App">
      <ShowArea />
      <Button />
    </div>
  );
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

Ⅴ、状态管理组件创建编写

很明显 ShowArea 组件需要一个颜色状态,所以我们创建 color.js 来处理状态。 {props.children}传值给子组件(多个)

// color.js
import React, { createContext } from "react";

// 创建 context
export const ColorContext = createContext({});

/**
 * 创建一个 Color 组件
 * Color 组件包裹的所有子组件都可以通过调用 ColorContext 访问到 value
 */
export const Color = props => {
  return (
    <ColorContext.Provider value={{ color: "blue" }}>
      {props.children}
    </ColorContext.Provider>
  );
};

Ⅵ、引入状态

修改 index.js,让所有子组件都可以访问到颜色。

// index.js

import { Color } from "./color";

function App() {
  return (
    <div className="App">
      <Color>
        <ShowArea />
        <Buttons />
      </Color>
    </div>
  );
}

Ⅶ、获取状态

在 ShowArea 组件中获取颜色 直接引入 然后使用useContext进行获取使用

// ShowArea.js

import { ColorContext } from "./color";

const ShowArea = props => {
  const { color } = useContext(ColorContext);
  return <div style={{ color: color }}>字体颜色展示为{color}</div>;
};

Ⅷ、创建 reducer

接着在 color.js 中添加 reducer, 用于处理颜色更新的逻辑。 然后使用useReducer传入reducer进行处理

import React, { createContext, useReducer } from "react";
// 创建 context
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  
  }
}

/**
 * 创建一个 Color 组件
 * Color 组件包裹的所有组件都可以访问到 value
 */
export const Color = props => {
  const [color, dispatch] = useReducer(reducer, 'blue')
  return (
    <ColorContext.Provider value={{color, dispatch}}>
      {props.children}
    </ColorContext.Provider>
  );
};

Ⅸ、更新状态

为按钮添加点击事件,调用 dispatch 就可以更新颜色了。

// buttons.js

import React, { useContext } from "react";
import { colorContext, UPDATE_COLOR } from "./color";

const Buttons = props => {
  const { dispatch } = useContext(colorContext);
  return (
    <React.Fragment>
      <button
        onClick={() => {
          dispatch({ type: UPDATE_COLOR, color: "red" });
        }}
      >
        红色
      </button>
      <button
        onClick={() => {
          dispatch({ type: UPDATE_COLOR, color: "yellow" });
        }}
      >
        黄色
      </button>
    </React.Fragment>
  );
};

export default Buttons;

总结

  • useContext 创建全局状态,不用一层一层的传递状态。
  • useReducer 创建 reducer 根据不同的 dispatch 更新 state。
  • 代码写到哪里状态就加到哪里,不用打断思路跳到 redux 里面去写。
  • 全局状态分离,避免项目变大导致 Redux 状态树难以管理。

所以 useContext + useReducer 完全可以替代 React 进行状态管理。但是目前绝大多数 React 项目仍在使用 Redux,所以深入学习 Redux 还是很有必要的。


useMemo

useMemo主要用来解决使用React hooks产生的无用渲染的性能问题。使用function的形式来声明组件,失去了shouldCompnentUpdate(在组件更新之前)这个生命周期,也就是说我们没有办法通过组件更新前条件来决定组件是否更新。而且在函数组件中,也不再区分mountupdate两个状态,这意味着函数组件的每一次调用都会执行内部的所有逻辑,就带来了非常大的性能损耗。

useMemouseCallback都是解决上述性能问题的。

  • useMemo缓存一个值,即return的那个,当组件依赖变化时,它的依赖没变换就不会重新执行
  • 一般配合usecallback使用
import React, { useState, useMemo } from 'react';
// , PureComponent, memo, useState, useMemo, useCallback
import './App.css';

function Count(props) {
  return <div>子组件count值:{props.count}</div>
}

function App(props) {

  const [count, setCount] = useState(0);

  //第一个参数是要执行的函数
  //第二个参数是执行函数依赖的变量组成的数据
  //这里只有count发生变化double才会重新计算
  const double = useMemo(() => {
    return count * 2;
  }, [count])

  return (
    <div className="app">
      <p>父组件count值:{count}</p>
      <Count count={count} />
      <button
        onClick={() => {
          setCount(count + 1)
        }}>
        Add
     </button>
      <div>double值:{double}</div>
    </div>
  )
}

export default App;

如果useMemo返回的是一个函数,则可以用useCallback省略顶层的函数。 将包裹的onClick函数用useCallback包裹:

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

//这样返回的函数就会在组件重渲染时产生相同的句柄
//useMemo使用
// const onClick = useMemo(() => {
//   //这里返回的依然是函数
//   return () => {
//     console.log('click')
//   }
// }, []);

//useCallback使用
const onClick = useCallback(() => {
    console.log('click')
}, []);

因此useCallbackuseMemo的变体。

useMemo(()=>return fn);
//等价
useCallback(fn);

useCallback

useCallback的作用其实是用来避免子组件不必要的reRender。

useCallback的用法与useState的用法基本一致,但最后会返回一个函数,用一个变量保存起来。

返回的函数a会根据b的变化而变化,如果b始终未发生变化,a也不会重新生成,避免函数在不必要的情况下更新。记得子组件导出时使用memo包裹一下,其作用是对组件前后两次进行浅对比,阻止不必要的更新。

用法:父子组件函数式传参

既然使用useCallback减少了函数式参数不必要的更新,子组件收到的参数不变,自然也不会更新,从而减少了组件间不必要的更新。

import React, { useState, useCallback, useEffect } from 'react';

const set = new Set();

export default function Parent() {
  const [count, setCount] = useState(1);
  const [val, setVal] = useState('');

  const callback = useCallback(() => {
    console.log(count);
    return count
  }, [count]); //count更新时执行
  set.add(callback); //借助ES6新增的数据类型Set来判断当依赖count变更时是否返回新的函数
  console.log(set);


  return <div>
    <h4>parent count:{count}</h4>
    <h4>set size:{set.size}</h4>
    <div>
      <button onClick={() => setCount(count + 1)}>+</button>
      <input value={val} onChange={event => setVal(event.target.value)}/>
    </div>

    <Child callback={callback} />
  </div>;
}

const Child = ({callback}) =>{
  const [count,setCount] = useState(0);
  
  useEffect(()=>{
    setCount(callback())
  },[callback]) //当callback更新时执行callback函数,得到parent组件最新的count

  return <>
    child count:{count}
  </>
}

我们可以看到,每次修改count,set.size就会+1,这说明useCallback依赖变量count,count变更时会返回新的函数;而val变更时,set.size不会变,说明返回的是缓存的旧版本函数。

实用场景
有一个父组件,其中包含子组件,子组件接收一个函数作为props;通常而言,如果父组件更新了,子组件也会执行更新;但是大多数场景下,更新是没有必要的,我们可以借助useCallback来返回函数,然后把这个函数作为props传递给子组件;这样,子组件就能避免不必要的更新。

useMemo + useCallback优化渲染性能

useMemouseCallback接收的参数都是⼀样,第⼀个参数为回调,第⼆个参数为要依赖的数据。

共同作⽤

  • 仅仅 依赖数据 发⽣变化, 才会重新计算结果,也就是起到缓存的作⽤。

两者区别

  • useMemo 计算结果是 return 回来的值, 主要⽤于 缓存计算结果的值 ,应⽤场景如: 需要 计算的状态

  • useCallback 计算结果是 函数, 主要⽤于 缓存函数,应⽤场景如: 需要缓存的函数,因为函数式组件每次任何⼀个 state 的变化 整个组件 都会被重新刷新,⼀些函数是没有必要被重新刷新的,此时就应该缓存起来,提⾼性能,和减少资源浪费。

应用场景

比如说父组件有一个筛选组件和一个table组件,筛选组件只依赖筛选条件filterOption,table组件只依赖dataSource。

如果dataSource变了,父组件就要更新,但这时其实筛选组件是不需要更新的 。

这时候,查询函数要定义在父组件中,要把它usecallback起来,再传给filter组件,filter用useMemo包起来 筛选条件作为依赖。

useRef

  • useRef获取React JSX中的DOM元素,获取后你就可以控制DOM的任何东西了。但是一般不建议这样来作,React界面的变化可以通过状态来控制。
  • useRef来保存变量(有了useContext这样的保存其实意义不大)

useRef获取DOM元素

import React, { useRef} from 'react';
function Example8(){
    const inputEl = useRef(null)
    const onButtonClick=()=>{ 
        inputEl.current.value="Hello ,JSPang"
        console.log(inputEl) //输出获取到的DOM节点
    }
    return (
        <>
            {/*保存input的ref到inputEl */}
            <input ref={inputEl} type="text"/>
            <button onClick = {onButtonClick}>在input上展示文字</button>
        </>
    )
}
export default Example8

useRef保存普通变量

import React, { useRef ,useState,useEffect } from 'react';
function Example8(){
    const inputEl = useRef(null)
    const onButtonClick=()=>{ 
        inputEl.current.value="Hello ,useRef"
        console.log(inputEl)
    }
    //-----------关键代码--------start
    const [text, setText] = useState('jspang')
    const textRef = useRef()

    useEffect(()=>{
        textRef.current = text;
        console.log('textRef.current:', textRef.current)
    })
    //----------关键代码--------------end
    return (
        <>
            {/*保存input的ref到inputEl */}
            <input ref={inputEl} type="text"/>
            <button onClick = {onButtonClick}>在input上展示文字</button>
            <br/>
            <br/>
            <input value={text} onChange={(e)=>{setText(e.target.value)}} />
        </>
    )
}

export default Example8

这时候就可以实现每次状态修改,同时保存到useRef中了。

自定义hook

一些常见的自定义hook,用于满足业务功能

useWinSize:获取浏览器窗口尺寸

在实际开发中,为了界面更加美观。获取浏览器窗口的尺寸是一个经常使用的功能,这样经常使用的功能,就可以封装成一个自定义Hooks函数,记住一定要用use开头,这样才能区分出什么是组件,什么是自定义函数。

新建一个文件Example9.js,然后编写一个useWinSize,编写时我们会用到useStateuseEffectuseCallback所以先用import进行引入。

import React, { useState ,useEffect ,useCallback } from 'react';
function useWinSize(){
    const [ size , setSize] = useState({
        width:document.documentElement.clientWidth,
        height:document.documentElement.clientHeight
    })

    const onResize = useCallback(()=>{
        setSize({
            width: document.documentElement.clientWidth,
            height: document.documentElement.clientHeight
        })
    },[]) 
    useEffect(()=>{
        window.addEventListener('resize',onResize)
        return ()=>{
            window.removeEventListener('resize',onResize)
        }
    },[])

    return size;

}

调用

function Example9(){
    const size = useWinSize()
    return (
        <div>页面Size:{size.width}x{size.height}</div>
    )
}

export default Example9 

useUserContext:自定义Context的共享

Context的共享

首先我们在APP.js中定义两个context

export const UserContext = createContext()
export const TokenContext = createContext()

<UserContext.Provider value={{name:'lsh'}}>
    <TokenContext.Provider value={{token:'asd'}}>
    <ContextShare/>
</TokenContext.Provider>
</UserContext.Provider>

user-hook.js

将UserContext,TokenContext导入,然后通过useContext来赋值给我们定义的变量,然后给它return出去,这样我们在不同的组件也都能获取到

import React, { useContext } from 'react'
import {UserContext,TokenContext} from '../App'
export default function useUserContext() {
  const user = useContext(UserContext)
  const token = useContext(TokenContext)
  return [user,token]
}

之后在其他页面怎么用呢?

import React, { useContext } from 'react'
import useUserContext from '../hook/user-hook'
export default function ContextShare() {
  // 核心代码
  const [user,token] = useUserContext()
  console.log(user,token)
  return (
    <div>
    </div>
  )
}

useScrollPostion:获取页面滚动位置

scroll-postion.js

import React,{useState,useEffect} from 'react'
export default function useScrollPostion() {
  const [scrollPosition, setScrollPostion] = useState(0)
  useEffect(() => {
    const handleScroll = () => {
      setScrollPostion(window.scrollY)
    }
    document.addEventListener('scroll', handleScroll);
    return () => {
      document.removeEventListener('scroll', handleScroll)
    }
  },[])
   
   return [scrollPosition]

  return (
    <div>
    </div>
  )
}

调用

import React, { useEffect, useState } from 'react'
import useScrollPostion from '../hook/scroll-postion'

export default function CustomScroll() {
    const position = useScrollPostion() 
    return (
        <div style={{ padding: '1000px 0 ' }}>
            gogoing
            <h2>{position}</h2>
        </div>
    )
}

useLocalStorage:localStorage的存储

local-storage-hook.js

import React,{ useState, useEffect } from 'react'

export default function useLocalstoragehook(key) {

  const [name, setName] = useState(() => {
    const name = JSON.parse(localStorage.getItem(key))
    return name
  })

  useEffect(() => {
    // localStorage.setItem('name', name)
    localStorage.setItem(key, JSON.stringify(name))
  }, [name])
   
  return [name,setName]

  return (
    <div>
    </div>
  )
}

组件调用

import React, { useState, useEffect } from 'react'

import useLocalstoagehook from '../hook/local-storage-hook'

export default function CustomLocalStorage() {
  
  const [name,setName]= useLocalstoagehook('name')

  return (
    <div>
      {name}
      <button onClick={e => setName('lsh')}>设置</button>
    </div>
  )
}

usePersistFn:返回一个永远不变的函数引用

接收一个函数,返回一个永远不变的函数引用,在这个函数中每次都能拿到最新的state值

function usePersistFn(fn) {
  const fnRef = useRef(fn);
  fnRef.current = fn;

  const persistFn = useRef();
  if (!persistFn.current) {
    persistFn.current = function (...args) {
      return fnRef.current.apply(this, args);
    };
  }

  return persistFn.crrent;
}

参考

https://blog.csdn.net/u013565133/article/details/104001901

https://jspang.com/article/50

《react hooks 中useCallback的作用,实例讲解》https://blog.csdn.net/sinat_36146776/article/details/110549211

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值