React-hook

目录

useState

useEffect

useReducer

useContext

 useCallback

useMemo

总结

 局限性(规则)


 

在学习Hook之前,先了解hook是什么,为什么要用hook

hook是什么?

Hook 是 React 16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性,例如部分的生命周期函数。需要注意的是Hook不能在类组件中使用。

也就是说,使用hook就是为了在函数式组件中使用到类组件中的某些特性。

Q:那为什么要用hook,为什么不直接创建类组件?

那么,带着这个问题往下看


useState

useState接收一个初始值,返回的是一个解构出来的数组,第一个是当前状态(似state),第二个是状态的更新函数(似setState),更新函数与类组件的setState不同的是,useState的更新函数会将状态替换(replace)而不是合并(merge)。

使用场景:函数组件中需要用到状态值,类似react类组件的state

在以下例子当中,没有用到类组件中易变的this,没有this指向问题,代码简单明了

import React, { useState } from 'react';

function Example() {
  // 声明一个新的叫做 “count” 的 state 变量,0是默认值
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}
export default Example;

useState后面可以传值也可以是个函数

const [ count, setCount ] = useState(() => {
  return props.count || 0
})

 当我们在使用 useState 更新值时,组件重新渲染。若传入的更新值时值不变时,组件不会重新渲染。


 

useEffect

使用场景:useEffect副作用,使函数组件拥有了类似react的声明周期。useEffect会在组件每次render之后调用。使用useEffect 可以更好的处理异步请求。

熟悉React class 的生命周期函数,你可以把 useEffect Hook 看做 componentDidMount,componentDidUpdate 和 componentWillUnmount 这三个函数的组合

useEffect(() => {}, [array]);

useEffect 可以接收两个参数,第一个是执行函数,可以return。第二个参数为数组,数组中有指定的参数值时,会根据该参数改变调用。

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

function Example() {
  const [count, setCount] = useState(0);
  const [dataSources, setDataSources] = useState([]);

  /* 
   * 情况一:useEffect无第二个参数 
   */
  //组件初始化和render后,都会执行该useEffect
  useEffect(() => {
    console.log("相当于生命周期:componentDidMount+componentDidUpdate")
  });

  /* 
   * 情况二:useEffect有第二个参数 
   */
  //第二个参数为空数组时:组件初始化才执行
  useEffect(() => { 
    console.log("相当于生命周期:componentDidMount"); 
  }, []);
  
  //第二个参数为指定状态值时:组件初始化时和dataSources发生变化才执行
  useEffect(() => { 
    console.log("相当于生命周期:componentDidMount")
    console.log("相当于依赖dataSources状态值的生命周期:componentDidUpdate")
  }, [dataSources]);

  //执行函数内return一个函数:初始化时执行函数体,组件卸载unmount时执行return后的函数
  useEffect(() => {
    console.log("相当于生命周期:componentDidMount")
    // 执行函数中直接使用return返回一个函数,这个函数会在组件unmount时执行。
    return () => {
      console.log('相当于声明周期:componentWillUnmount');  
    }
  }, []);

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}
export default Example;

与 类组件中的componentDidMount 或 componentDidUpdate 不同,useEffect是异步的,使用useEffect 调度的 effect不会阻塞浏览器更新屏幕 


 

useReducer

使用场景:由于useState的更新函数采用的是替换的方式,当我们要在函数组件中处理复杂状态时,我们可以使用useReducer

const [state, dispatch] = useReducer(reducer, initialArg, init);

跟redux中的reducer类似,有个配套使用的dispatch

import React, { useReducer } from 'react';
// 修改一个状态对象中的某属性,如果用useState,则要重新赋值一个对象,每个属性名都要写一遍
const initialState = {count: 0, name: '名字', age: 1};

function reducer(state, action) {
  switch (action.type) {
    case 'increment':
      return {...state, count: state.count + 1};
    case 'decrement':
      return {...state, count: state.count - 1};
    default:
      throw new Error();
  }
}

function Example() {
  const [state, dispatch] = useReducer(reducer, initialState);
  return (
    <>
      Count: {state.count}
      <button onClick={() => dispatch({type: 'decrement'})}>-</button>
      <button onClick={() => dispatch({type: 'increment'})}>+</button>
    </>
  );
}
export default Example;

init是个函数,并以initialArg为入参,用于操作initialArg,返回自定义的初始值。

import React, { useReducer } from 'react';
function init(initialCount) {
  return {count: initialCount};
}
function reducer(state, action) {
  switch (action.type) {
    case 'increment':
      return {count: state.count + 1};
    case 'decrement':
      return {count: state.count - 1};
    case 'reset':
      return init(action.payload);
    default:
      throw new Error();
  }
}
function Example({initialCount}) {
  const [state, dispatch] = useReducer(reducer, initialCount, init);
  return (
    <>
      Count: {state.count}
      <button
        onClick={() => dispatch({type: 'reset', payload: initialCount})}>
        Reset
      </button>
      <button onClick={() => dispatch({type: 'decrement'})}>-</button>
      <button onClick={() => dispatch({type: 'increment'})}>+</button>
    </>
  );
}
export default Example;


 

useContext

使用场景:如果需要在深层次组件之间共享状态,可以使用 useContext()。context 提供了一种在组件之间共享 props 的方式,而不必显示地通过组件树的逐层传递 props; useContext 钩子比原始 class 组件中使用 context 更为方便;

import React, { useContext } from 'react';

const TestContext = React.createContext({});

const Navbar = () => {
  const { username } = useContext(TestContext);

  return (
    <div className="navbar">
      <p>{username}</p>
    </div>
  );
};

const Messages = () => {
  const { messageDetail } = useContext(TestContext);

  return (
    <div className="messages">
      <p>1 message for {messageDetail}</p>
    </div>
  );
};

const App = () => {
  return (
    <TestContext.Provider
      value={{
        username: 'superawesome',
        messageDetail: 'hello world',
      }}
    >
      <div className="test">
        <Navbar />
        <Messages />
      </div>
    </TestContext.Provider>
  );
};
export default App;

当然在类组件中 ,provider提供的值也可以声明static contextType后直接用this.context获取,但是这种方法仅限于类组件,不支持多个Context嵌套。

相比之下useContext()的方式,最为简单。 


 

 useCallback

使用场景:useCallback用来缓存方法,类似react组件构造函数里面定义的:this.onChange =
this.onChange.bind(this),返回一个缓存的函数

先看React.memo的使用场景

//使用场景:React.memo就类似React.PureComponent,专门用于纯函数组件,起作用是对内部对象进行
//浅比较,判断是否重新渲染。
import React, { useState, useCallback } from 'react';
function Example() {
  const [count, setCount] = useState(0);
  const [list, setList] = useState([]);

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>addCount</button>
      <button onClick={() => setList([...list, 1])}>changeList</button>
      <Child list={list} />
    </div>
  );
}
此时,无论是改变count状态值,还是name状态值,Child组件都会重新渲染
//const Child = (props) => {
//    console.log("进入了组件child")
//    return (
//         <div>这里是child:list为{props.list.join(',')}</div>
//    )
//}

使用React.memo,就可以根据父组件传入的props也就是list是否更新来进行渲染
const Child = React.memo((props) => {
  console.log("进入了组件child")
  return (
    <div>这里是child:{props.list.join(',')}</div>
  )
})

export default Example;

接着上面的例子,我们在子组件上加入一个函数

const handleChange = () => {
    console.log(`selected`);
}
<Child list={list} handleChange={handleChange} />

此时,child组件又出现了同样的问题,现在就要用useCallback()来解决这个问题,这时状态值变化才重新改变handleChange方法

const handleChange = useCallback(() => {
  console.log(`selected`);
},[])// 或[list]


 

useMemo

使用场景:useMemo函数用于缓存需计算操作的状态值,类似Vue的计算属性computed。

第一个参数为计算函数,且必须return返回一个结果,返回一个缓存的值,第二个参数是一个数组[],useMemo执行依赖于次数组的状态值

import React, { useState, useCallback, useMemo } from 'react';
function Example() {
  const [count, setCount] = useState(0);
  const [list, setList] = useState([]);

  const [a, setA] = useState(0)
  const [b, setB] = useState(0)

  const handleChange = useCallback(() => {
    console.log(`selected`);
  },[])

  //使用useMemo缓存一个计算值,计算函数的执行依赖于状态值a和b,当a和b变化时才执行计算函数
  const memoizedValue = useMemo(() => a + b, [a, b]);
  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>addCount</button>
      <button onClick={() => setList([...list, 1])}>changeList</button>
      <button onClick={() => { setA(a+1)}}>点我A</button>
      <button onClick={() => { setB(b+1)}}>点我B</button>
      <Child list={list} memoizedValue={memoizedValue} handleChange={handleChange} />
    </div>
  );
}
const Child = React.memo((props) => {
  console.log("进入了组件child")
  return (
    <div>这里是child:list为{props.list.join(',')}, 计算总和:{props.memoizedValue}</div>
  )
})
export default Example;


 

总结

回答为什么要使用hook

  1. 使状态逻辑复用变得简单可行:将组件状态逻辑提取到可重用的函数中,使得这些逻辑可以单独测试并复用,且使你在无需修改组件结构的情况下复用状态逻辑。
  2. 简化生命周期:在类组件当中按照严格的生命周期函数来进行创建(componentDidMount)销毁(componentWillUnmount),或者说在更新(componentDidUpdate)时常常遇到几个不相关逻辑的复杂交错。解决 class 中生命周期函数经常包含不相关的逻辑,但又把相关逻辑分离到了几个不同方法中的问题
  3. 不用考虑 Class的this指向:在类组件中,为了解决 this 不符合预期的问题,还必须用 bind 或箭头函数。函数组件更加契合 React 框架的设计理念。

 

 局限性(规则)

hook暂时还不能完全地为函数组件补齐类组件的能力,比如没有完整的生命周期钩子函数

在使用层面有着严格的规则约束

  1. 只能在函数最外层调用 Hook。不要在循环、条件判断或者子函数中调用。
  2. 只能在 React 的函数组件中调用 Hook。不要在其他 JavaScript 函数中调用。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值