1. useState 使用demo

//  App 组件
function App() {
    let [a, setA] = React.useState(0);
    // 改变a的值
    const changeA = () => {
        setA(a + 1);
        console.log("render");
    };
    return (
        <>
            <div>{a}</div>
            <button onClick={changeA}> +</button>
        </>
    );
}
render();
// 渲染
function render() {
    ReactDOM.render(<App />, document.getElementById("root"));
}

2. 实现效果


效果如下,每次点击加1,同时更新视图:
在这里插入图片描述

3.实现原理解析:

1 . 调用 useState() 方法, 传入一个参数(如上demo,假如为数字0), 所以,我需要有个 useState() 方法, 同时导出了 一个数组, 俩个元素, 第一个元素是要渲染的值, 第二个值是一个函数

// 模拟这个场景
function useState(value){
  function setState(){}
  return [value, setState];
}

2.useState 内部的 setState() 接收这个参数, 第一次初始化的时候,就是默认的值, 但之后的值,每次更新都会通过调用 setA() 函数(其实内部走的是setState 方法),同时传入一个新的值,覆盖掉老的值

// 模拟 setA 函数
function useState(value){
  // 次函数会接收一个新的值
  function setState(newValue){
   	value = newValue
   	// 调用更新视图
  }
  return [value, setState];
}

4. 按以上的思路实现代码

function useState(value) {
   function setState(newValue) {
       value= newValue;
       render();
       console.log(newValue);
       // 调用更新视图
   }
   return [value, setState];
}

//  App 组件
function App() {
   let [a, setA] = useState(0);

   // 改变a的值
   const changeA = () => {
       setA(a + 1);
       console.log("render");
   };

   return (
       <>
           <div>{a}</div>
           <button onClick={changeA}> +</button>
       </>
   );
}

// 渲染
function render() {
   ReactDOM.render(<App />, document.getElementById("root"));
}
render();

效果如下,发现并没有渲染视图, 同时打印发现,结果一直是1, 并没有根据上次的值进行累加
在这里插入图片描述

5. 分析没有渲染预想结果的原因

1.分析如下代码,首先第一个原因, 把每次传入进来的新的value的值 虽然进行了保存, 但每次调用setA() 传入的 a+1 值依赖的都是初始值 0,所以每次都会打印1

function useState(value) {
   function setState(newValue) {
       value= newValue;
       render();  // 调用更新视图
       console.log(newValue); // 永远是1
   }
   return [value, setState];
}

2.改写, 首先定义一个全局变量,每次调用时, 先取一下上次最后保存的值, 如果有,就取出来上次的最后一个值,没有的话,就取默认值,也就是这里利用一下闭包

let lastValue;
function useState(value) {
	// 如果有,就取出来上次的最后一个值,没有的话,就取默认值
   lastValue = lastValue || value;
   function setState(newValue) {
       // 每次改变都保存更新的值
       lastValue = newValue;
       render();
       // 调用更新视图
   }
   return [lastValue , setState];
}

6. 改写后实现结果

在这里插入图片描述

7. 当使用多个 useState时,数据不冲突的原理

先看上边的demo,自己实现的useState,调用多次,是有bug的, 更新a时, b也会同时更新

7.1 俩按钮的demo事件

import React from "react";
import ReactDOM from "react-dom";

let lastValue;
function useState(value) {
    lastValue = lastValue || value;
    function setState(newValue) {
        lastValue = newValue;
        render();
        // 调用更新视图
        console.log(lastValue);
    }
    return [lastValue, setState];
}

//  App 组件
function App() {
    let [a, setA] = useState(0);
    let [b, setB] = useState(1);

    // 改变a的值
    const changeA = () => {
        setA(a + 1);
    };
    // 改变b的值
    const changeB = () => {
        setB(a + 1);
    };
    return (
        <>
            <div>{a}</div>
            <button onClick={changeA}> a+</button>
            <hr />

            <div>{b}</div>
            <button onClick={changeB}> b+</button>
        </>
    );
}

// 渲染
function render() {
    ReactDOM.render(<App />, document.getElementById("root"));
}
render();

a按钮b按钮,都会同时更新,明明是俩变量,但是确同时更新
在这里插入图片描述

7.2 分析上边出现的原因,以及怎么解决保证具体是哪个state

分析:

  1. 怎么让同一个函数的调用,根据各自的参数, 产生对应的值,怎么来区分
  2. useState 会调用多次,每次怎么知道是谁的值,而lastState ,刚好保存的又是上次对应的自己的值
  3. 此时,我可以想到怎么区分, 一种是使用对象,或者map,但是用map,或者对象,但这里明显它俩不合适,不知道key怎么定义,还有就是数组,通过索引来找到具体的操作的是哪个 ,无法像下边这样写
  • 对象不合适
let index = 0;
let lastValue = {};
function useState(value) {
    // 明显的 lastValue  = {index:1}, 而我期望的是{"0":1}
    lastValue = lastValue[index] || { index: value };
    console.log(lastValue);
    let curIndex = index;
    function setState(newValue) {
        lastValue[curIndex] = newValue;
        render();
        // 调用更新视图
        console.log(lastValue);
    }
    return [lastValue[curIndex++], setState];
}

7.3 利用数组的方式实现

采用数组索引的方式, 大致思路:

  1. 两个全局变量, 一个用于 将传入的值,存入到数组中,一个是默认的初始索引值0,
  2. 先取值,如果 数组 [index] 能取到值,就用 数组 [index], 取不到 就使用用户传入的值
  3. 调用 内部的 闭包函数 setState 时, 让 数组 [index] = newValue 新值
  4. 所以每次导出的值的索引都要加1, 使用后置++,以保证再下次再调用时,索引向上递增1,
  5. 同时,还要保存这个索引,因为它每次都会变

7.4 实现代码

let state = []; 
let index = 0;
function useState(value) {
    // 拿到本次索引的值
    state[index] = state[index] || value;
    // 保存本次的index
    let cutIndex = index;
    function setState(param) {
        state[cutIndex] = param;
        render();
    }
    // 每调用一次, 索引再下次再调用时就+1
    return [state[index++], setState];
}

function App() {
    let [a, setA] = useState(0);
    let [b, setB] = useState(1);
    const changeA = () => setA(a + 1);
    const changeB = () => setB(b + 1);
    return (
        <>
            <div>{a}</div>
            <button onClick={changeA}> +</button>

            <hr />
            <div>{b}</div>
            <button onClick={changeB}> +</button>
        </>
    );
}

function render() {
    ReactDOM.render(<App />, document.getElementById("root"));
}
render();

但你每次点击,索引索引再变, 但是视图并没有更新, 此时还需要每次render时都重置下索引

// ...略
function render() {
    index = 0;
    ReactDOM.render(<App />, document.getElementById("root"));
}
render();

在这里插入图片描述

8. 调用了自定义的setXXX 还是不更新视图

通常不更新视图,是因为你的数据原因,基本数据结构类型 不会 出现这种问题的, 引用数据类型根据你写法的不同是会出现这种情况的, 你每次调用自己的 setXXX 方法, 如果传入的是同一个引用的话, 是不会刷新视图,如下:

let obj = { num: 5 };
function App() {
    let [a, setA] = React.useState(obj);
    // 改变obj的a
    const changeA = () => {
        obj.num += 1;
        console.log(obj.num);
        setA(obj);
    };
    return (
        <>
            <div>{a.num}</div>
            <button onClick={changeA}> +</button>
        </>
    );
}
ReactDOM.render(<App />, document.getElementById("root"));

在这里插入图片描述
虽然数据再变, 但视图没有刷新, 所以对于引用数据类型,必须保证每次传入的不是同一个引用才会引起视图的更新,所以每次都是一个新的引用地址,对于复杂的数据结构,可以使用Object.create(), {},[],Object.assign(),等 处理一下

let obj = { num: 5 };
function App() {
    let [a, setA] = React.useState(obj);

    // 改变obj的a
    const changeA = () => {
        setA({ num: a.num + 1 });
    };
    return (
        <>
            <div>{a.num}</div>
            <button onClick={changeA}> +</button>
        </>
    );
}

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

在这里插入图片描述
不允许调用 forceUpdate 方法, 因为此方式只能类组件使用,就像 hooks 只能用在函数组件中一样

Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐