前置简介
上一节(https://juejin.im/post/6867429672347500551)说了如何使用props,这一节介绍一下state的使用。
代码在:https://codesandbox.io/s/dawn-shape-bofqp?file=/src/index.js
index.js
import React from "react";
import ReactDOM from "react-dom";
import "./styles.css";
function App() {
return (
<div className="App">
父节点
<Son />
</div>
);
}
class Son extends React.Component {
constructor() {
super();
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>
<Grandson />
</div>
);
}
}
const Grandson = (props) => {
const [n, setN] = React.useState(0);
return (
<div className="Grandson">
孙子 n:{n}
<button onClick={() => setN(n + 1)}>+1</button>
</div>
);
};
const rootElement = document.querySelector("#root");
ReactDOM.render(<App />, rootElement);
在类组件中使用state
上面的index.js中的Son组件,有如下代码:
add() {
this.setState({ n: this.state.n + 1 });
}
通过调用this.setState来对n数字就行+1操作,
这里为什么不可以直接通过 this.state.n++
或者 this.state.n = this.state.n + 1
来改变呢?
因为React其实并没有去监听this.state.n这个属性,即使这个属性发生了变化,React也是无感知的,这里和Vue以及Angular是有区别的,
所以我们需要调用setState方法对React进行通知,使其刷新视图,
看下方代码:
add() {
this.state.n += 1
this.setState(this.state);
}
读者可以用这段代码去实验一下,是可以使得视图刷新的,但是我们并不推荐这么做,因为React要遵循一个原则,数据不可变性
,以前的对象不要再去改动它了,生成一个新的对象,如下:
add() {
this.setState({n: this.state.n + 1});
}
尽管上述的写法已经没有问题了,但是如果我们想在setState方法后打印log最新的值,我们一般会这么写:
add() {
this.setState({n: this.state.n + 1});
console.log(this.state.n)
}
但是这种写法其实并不会生效,因为setState方法是异步去执行的,所以我们要换一种写法:
add() {
this.setState((state) => {
const n = state.n + 1
console.log(n)
return {n: n}
});
}
我们在this.setState参数中传递了一个函数,这个函数中我们对 n 这个数字进行操作,并进行打印log,
最后返回一个新的对象用来告诉React,这么做的好处是方便我们在赋值操作的前后做一些额外处理,并且不会因为setState方法执行的异步造成一些困惑。
在函数组件中使用state
上面的index.js中的Grandson组件,有如下代码:
const [n, setN] = React.useState(0);
React.useState 返回两个值,一个是 n 本身的值也就是方法中的 0,另外一个是setN方法,用来改变 n 的值,
所以在函数组件中通过 React.useState 获取到的第二个值,来操作一个属性的值就好。
// 这里是析构赋值,React.useState(0)返回了一个数组,第一个是值本身,第二个是修改值的方法,接收的属性名可以自己定义
const [n, setN] = React.useState(0);
注意 setN 并不是直接去修改 n 的值的,而是生成了一个新的state,遵循了数据不可变性原则,这个后面会详细来解释。
类组件 setState 注意事项
-
this.state.n += 1 无效?
其实 n 已经改变了,只是UI不会自动更新, 调用 setState 才会触发UI更新 (并且更新是异步的),因为 React 没有像 Vue 监听 data 一样监听 state,所以需要手动调用 setState 去通知React进行更新操作。
-
setState 会异步更新UI
setState之后,state不会马上改变,立刻读取 state 会失败,更推荐的方式是 setState(函数)。
-
this.setState(this.state)
这种方式虽然也可以改变数据,但是React希望我们不要更改旧state,所以不推荐使用,常用的代码是
setState({n:state.n + 1})
函数组件注意事项
-
跟类组件相似,需要通过setX(新值)来更新UI。
-
跟类组件不同的地方,没有this,一律使用参数和变量。
两种编程模型
Vue的编程模型
一个对象对应一个虚拟DOM,当对象的属性改变时,把属性相关的DOM节点全部更新。
PS(Vue为了其他考量,也引入了虚拟DOM和DOM diff)。
例如将 n 放在模板中,后续对 n 做直接更改,那么视图中的 n 也跟着做相应的更改。
React的编程模型
一个对象,对应一个虚拟DOM。
另一个对象,对应另一个虚拟DOM。
对比两个虚拟DOM,找出不同之处(DOM Diff) 最后局部更新DOM。
例如刚开始创建了一个 n 对象,放到视图中,后面要新创建一个 n 对象,再放到视图中,React进行对比,做出相应的改变。