React Hook 指南 useState() useEffect() useReducer() 自定义 Hook

useState()状态管理

import React, { usestate } from 'react" ;function Bulbs() {
const [setOn] = useState( false) ; //设置初始值为false
const lightOn= ()=>set0n( true ); //改变初始值
const lightOff =()=> setOn( false );
return (
<div className={on ? 'bulb-on' : 'bulb-off '}/>
<button onclick={lightOn}>开灯</ button>
<button onclick={light0ff}>关灯</button></>
);}


setState 和 useState 的区别

1.获取修改后的值
setState

this.state={
count:0
}
this.setState({
  count: this.state.this.state=count + 1
}, () => {
  console.log(this.state.count); // 这里是监控到的最新值
})

useState

const [ count, setCount ] = useState(0);
setCount(1); 
useEffect(() => {
  console.log(count); // 这里是监控到的最新值
}, [ count ]);

setState 和 useState 同样场景的应用

setState

class Counter extends Component {
  state = { count: 0 };

  log = () => {
    this.setState({
      count: this.state.count + 1
    });
    setTimeout(() => {
      console.log(this.state.count);
    }, 3000);
  };

  render() {
    return (
      <div>
        <p>You clicked {this.state.count} times</p>
        <button onClick={this.log}>Click me</button>
      </div>
    );
  }
}
// 3 3 3

首先 state 是 Immutable 的,setState 后一定会生成一个全新的 state 引用。
但 Class Component 通过 this.state 方式读取 state,这导致了每次代码执行都会拿到最新的 state 引用,所以快速点击三次的结果是 3 3 3。

useState

function Counter() {
  const [count, setCount] = useState(0);
  const log = () => {
    setCount(count + 1);
    setTimeout(() => {
      console.log(count);
    }, 3000);
  };
  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={log}>Click me</button>
    </div>
  );
}
// 0 1 2

useState 产生的数据也是 Immutable 的,通过数组第二个参数 Set 一个新值后,原来的值会形成一个新的引用在下次渲染时。
但由于对 state 的读取没有通过 this. 的方式,使得 每次 setTimeout 都读取了当时渲染闭包环境的数据,虽然最新的值跟着最新的渲染变了,但旧的渲染里,状态依然是旧值。

如何让 Function Component 也打印 3 3 3

借助一个新 Hook - useRef 的能力

function Counter() {
  const count = useRef(0);
  const log = () => {
    count.current++;
    setTimeout(() => {
      console.log(count.current);
    }, 3000);
  };

  return (
    <div>
      <p>You clicked {count.current} times</p>
      <button onClick={log}>Click me</button>
    </div>
  );
}

useRef 的功能:通过 useRef 创建的对象,其值只有一份,而且在所有 Rerender 之间共享。
count.current 赋值或读取,读到的永远是其最新值,而与渲染闭包无关,因此如果快速点击三下,必定会返回 3 3 3 的结果。
但这种方案有个问题,就是使用 useRef 替代了 useState 创建值.

如何不改造原始值也打印 3 3 3

function Counter() {
  const [count, setCount] = useState(0);
  const currentCount = useRef(count);
  useEffect(() => {
    currentCount.current = count;
  });
  const log = () => {
    setCount(count + 1);
    setTimeout(() => {
      console.log(currentCount.current);
    }, 3000);
  };
  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={log}>Click me</button>
    </div>
  );
}

一种最简单的做法,就是新建一个 useRef 的值给 setTimeout 使用,而程序其余部分还是用原始的 count

useState() 的无效调用

在条件中调用useState()是不正确的:

function Switch({ isSwitchEnabled }) {
  if (isSwitchEnabled) {
    // Bad
    const [on, setOn] = useState(false);
  }
  // ...
}

在嵌套函数中调用useState()也是不对的

function Switch( ) {
  let on = false;
  let setOn = ( ) => {};
  function enableSwitch() {
   [ on,setOn] = usestate(false);}
return (
<button onclick=fenableSwitch}>
    Enable light switch state
</button>
);
}

useEffect()状态管理

function Counter() {
  const [count, setCount] = useState(0);
  const currentCount = useRef(count);
  useEffect(() => {
    currentCount.current = count;
  });
  const log = () => {
    setCount(count + 1);
    setTimeout(() => {
      console.log(currentCount.current);
    }, 3000);
  };
  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={log}>Click me</button>
    </div>
  );
}
// 3 3 3

useEffect 会在第一次渲染之后和每次更新之后都会执行。
相当于componentDidMount,componentDidUpdate 和 componentWillUnmount 这三个函数的组合

useEffect 是处理副作用的,其执行时机在 每次 Render 渲染完毕后,换句话说就是每次渲染都会执行,只是实际在真实 DOM 操作完毕后。
我们可以利用这个特性,在每次渲染完毕后,将 count 此时最新的值赋给 currentCount.current,这样就使 currentCount 的值自动同步了 count 的最新值。

要注意的是,useEffect 也随着每次渲染而不同的,同一个组件不同渲染之间,useEffect
内闭包环境完全独立。对于本次的例子,useEffect 共执行了 四次,经历了如下四次赋值最终变成 3:
setTimeout 的例子,三次点击触发了四次渲染,但 setTimeout 分别生效在第 1、2、3 次渲染中,因此值是 0 1 2。
useEffect 的例子中,三次点击也触发了四次渲染,但 useEffect 分别生效在第 1、2、3、4 次渲染中,最终使 currentCount 的值变成 3。

useEfffect第二个参数不能使用引用类型

useEffect接受两个参数

  • 第一个参数是函数(这里叫effect函数),它的作用是,在页面渲染后执行这个函数。因此你可以把
    ajax请求等放在这里执行;

  • 第二个参数是一个数组,这里注意:
    在这里插入图片描述

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

function Example() {
  const [count, setCount] = useState({});

  useEffect(() => {
   setCount({test:"count是一个对象,使得页面死循环"})
  },[count]);

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}
//死循环

是因为在JavaScript中,{} === {}结果是false,{a:1} === {a:1}同样,由此造成了react以为两个值不同,就一直的渲染最终页面死循环

使用useEffect()时,有一点需要注意。如果有多个副效应,应该调用多个useEffect(),而不应该合并写在一起。

复杂状态用useReducer

用法 => const [state, dispatch] = useReducer(reducer, initialState);

function reducer(state, action) {
  switch (action.type) {
    case "increment":
      return {
        ...state,
        count: state.count + 1
      };
    default:
      return state;
  }
}

这样就可以通过调用 dispatch({ type: ‘increment’ }) 的方式实现 count 自增了。

假设需要编写一个最喜欢的电影列表。用户可以添加电影,也可以删除已有的电影
reducer管理电影的状态,有两种操作类型:
"add"将新电影插入列表
"remove"从列表中按索引删除电影

import React, { useReducer } from 'react ' ;

function reducer( state,action) {
switch (action.type) {
case 'add ' :
return [...state,action.item];case ' remove ' :
return [
...state.slice( 0,action.index ),...state.slice( action.index + 1)];
default:
throw new Error( );
}
}
function FavoriteMovies( ) {
const [state,dispatch] = useReducer( reducer,[{ name: 'Heat' }]
return (
);


自定义 Hook

是不是觉得每次都写一堆 useEffect 同步数据到 useRef 很烦?是的,想要简化,就需要引出一个新的概念:自定义 Hooks。
首先介绍一下,自定义 Hooks 允许创建自定义 Hook,只要函数名遵循以 use 开头,且返回非 JSX 元素,就是 Hooks 啦!自定义 Hooks 内还可以调用包括内置 Hooks 在内的所有自定义 Hooks。

function useCurrentValue(value) {
  const ref = useRef(0);

  useEffect(() => {
    ref.current = value;
  }, [value]);

  return ref;
}

useEffect 的第二个参数,dependences。dependences 这个参数定义了 useEffect 的依赖,在新的渲染中,只要所有依赖项的引用都不发生变化,useEffect 就不会被执行,且当依赖项为 [] 时,useEffect 仅在初始化执行一次,后续的 Rerender 永远也不会被执行。

function Counter() {
  const [count, setCount] = useState(0);
  const currentCount = useCurrentValue(count);

  const log = () => {
    setCount(count + 1);
    setTimeout(() => {
      console.log(currentCount.current);
    }, 3000);
  };

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={log}>Click me</button>
    </div>
  );
}
// 3 3 3
//这个例子中,我们告诉 React:仅当 value 的值变化了,再将其最新值同步给 ref.current。

eslint-plugin-react-hooks
这个插件,会自动订正你的代码中的依赖,想不对依赖诚实都不行!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值