【React全解3】React.useState原理详解,一次性搞懂useState

4,885 阅读4分钟

目录

  • useState简介
  • useState的简单使用
  • 手写实现useState
  • 总结
  • 参考

一、useState 简介

返回一个有状态值和一个函数来更新它。在初始渲染期间,返回的状态(状态)与作为第一个参数(initialState)传递的值相同。setState 函数用于更新状态。它接受一个新的状态值,并排队等待重新渲染该组件。

在后续重新渲染期间,useState 返回的第一个值将始终是应用更新后的最新状态。

注意: React 保证 setState 函数身份是稳定的,并且在重新渲染时不会改变。这就是为什么可以安全地从 useEffect 或 useCallback 依赖项列表中省略的原因。

useState 相当于我们在原生 JS 中定义变量。而只有使用这种方法定义变量,React 才能知道相应的变量。

二、useState 的简单使用

注意:在组件的定义中,首字母必须大写。

1、在类组件中使用 useState

class Classes extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      n: 0,
    };
  }
  add() {
    this.setState({ n: this.state.n + 1 });
  }
  render() {
    return (
      <div className="son">
        类组件--n:{this.state.n}
        <button onClick={() => this.add()}>+1</button>
        <Functions />
      </div>
    );
  }
}

2、在函数组件中使用 useState

const Functions = () => {
  const [n, setN] = React.useState(0);
  return (
    <div className="grandson">
      函数组件--n:{n}
      <button onClick={() => setN(n + 1)}>+1</button>
    </div>
  );
};

3、App 组件

使用 ReactDom.render()渲染 App。

function App() {
  return (
    <div className="app">
      useState的简单使用
      <Classes />
    </div>
  );
}
ReactDOM.render(<App />, document.getElementById("root"));

4、最终效果呈现

三、手写实现 useState

1、分析

  • useState 有两个状态,一个是 n,另外一个是 setN。

  • setN 是修改数据 n 的,将修改后的 n 存入 state。

  • setN 修改数据后一定会触发<App/>的重新渲染(re-render)。

  • useState 一定会从 state 读取最新的 n 值。

  • 每个组件都有自己的数据 state。

2、尝试实现 React.useState

  • 第1次写myUseState
function myUseState(initialValue) {
  let state = initialValue;
  const setState = (newValue) => {
    state = newValue; //更新state值,
    render(); //触发重新渲染
  };
  return [state, setState];
}
/* 粗糙的渲染 */
const render = () => {
  ReactDOM.render(<App />, document.getElementById("root"));
};
// 使用myUseState
const App = () => {
  const [n, setN] = myUseState(0);
  return (
	  <div classNam="App">
		 <p>n:{n}</p>
		 <button onClick={()=>{setN(n+1)}}>n+1</button> 
	  </div>
	  );
};
ReactDOM.render(<App />, document.getElementById("root"));

第一次总结

运行第一次写的myUseState时发现,页面完全没有变化。n值也没有发生改变。经分析,是因为每次调用myUseState时会重置state的值。经过改进,必须将state写在函数的外面

  • 第2次写myUseState
let _state;
function myUseState(initialValue) {
  _state = _state===undefined? initialValue:_state;
  const setState = (newValue) => {
    _state = newValue; //更新state值,
    render(); //触发重新渲染
  };
  return [_state, setState];
}
/* 粗糙的渲染 */
const render = () => {
  ReactDOM.render(<App />, document.getElementById("root"));
};
// 使用myUseState
const App = () => {
  const [n, setN] = myUseState(0);
  return (
	  <div classNam="App">
		 <p>n:{n}</p>
		 <button onClick={()=>{setN(n+1)}}>n+1</button> 
	  </div>
	  );
};
ReactDOM.render(<App />, document.getElementById("root"));

第二次总结

运行成功,页面中的n发生了变化!

但是问题又来了,如果一个组件用了两个useState怎么办?_state的值不是会发生冲突吗?继续改进完善myUseState。将_state做成数组

  • 第3次写myUseState
let _state=[];
let index=0;
function myUseState(initialValue) {
  int currentIndex=index;	//引入中间变量currentIndex就是为了保存当前操作的下标index。
  _state[currentIndex] = _state[currentIndex]===undefined? initialValue:_state[currentIndex];
  const setState = (newValue) => {
    _state[currentIndex] = newValue; 
    render(); 
  };
  index+=1;// 每次更新完state值后,index值+1
  return [_state[currentIndex], setState];
}
const render = () => {
  index=0;	//重要的一步,必须在渲染前后将index值重置为0,不然index会一种增加1
  ReactDOM.render(<App />, document.getElementById("root"));
};
// 使用myUseState
const App = () => {
  const [n, setN] = myUseState(0);
  const [m, setM] = myUseState(0);
  return (
	  <div classNam="App">
		 <p>n:{n}</p>
		 <button onClick={()=>{setN(n+1)}}>n+1</button> 
		 <p>m:{m}</p>
		 <button onClick={()=>{setM(m+1)}}>n+1</button> 
	  </div>
	  );
};
ReactDOM.render(<App />, document.getElementById("root"));

第三次总结

运行成功!n,m值都能单独的发生变化。

四、总结

本次我只是简单分析了React实现useState的思想和代码。至于React真正实现useState的方式肯定是比我手写的myUseState更加高级和高效的。

  • useState调用顺序

若第一次渲染的时候,n是第一个数据,m是第二个数据,k是第三个。...

则第二次渲染的时候必须保证完全的一致。

React不允许出现以下类似的代码,否则会出现报错信息:React Hook "React.useState" is called conditionally. React Hooks must be called in the exact same order in every component render react-hooks/rules-of-hooks

译:有条件地调用React Hook“ React.useState”。在每个组件中,必须以完全相同的顺序调用React Hooks来渲染react-hooks / rules-of-hooks

let n=2;
if(n%2){
	const [m,setM]=React.useState(0);
}
/* 以上代码React会直接报错 */

  • 每个组件命名的问题

App用了_state和index,那其他组件用什么?放在全局作用域重名了怎么办?

解决办法1:每个组件都创建一个_state和index。

解决办法2:放在组件对应的虚拟节点对象上

注意

React的节点应该是FiberNode,_state的真实名称为memorizedState,index的实现使用了链表。

五、参考

React Hooks原理

React官网