今天聊一聊React的三大基础。对于React来说,其三大基础分别是state,props和ref,下面分别展开说明。
前言
在说三大基础之前,先说下React的框架。React框架是Facebook于2014年开发并开源的,适合于Web端和移动端的跨平台开发框架。该框架有很多优点,其中一个比较大的优点就是虚拟Dom(VDOM)。在此框架之前,我们的网站的界面一般是通过真实设备树(DOM)进行刷新。使用真实DOM,最大的弊端就是每次刷新界面都是从头到尾的全量刷新,即使只是一个小小的按钮也是全量刷新。这样一来,性能就严重受到影响。所以Facebook就搞出了虚拟DOM:在界面首次更新的时候,新建一个虚拟DOM,并与真实DOM建立一一对应关系。之后的每次刷新,先和虚拟DOM进行Diff比较,比较出差异的部分,根据差异的部分进行更新真实DOM。这样刷新的就是真实DOM的一部分了,从而大大提高了性能。这就是React的虚拟DOM的概念和与真实DOM的简单描述。当然作为一个框架,还有很多其他的新概念,例如本文中要讲的state props和ref。
1.State
首先提下单页面应用(SPA)。
React采用的是单页面应用技术,即整个应用只有一个index.html文件。这个就区别于在此之前的多页面应用。在早期用jsp或php语言实现的Web网站就有很多个index.html文件。而React框架是只有一个index.html文件,并且作为框架的入口。
React应用从index.html文件入口进入后,就有展示不同的界面。这里的每个界面都是由许多个不同的组件(component)组合而成。这就像我们小时候玩积木一样,一个个小积木组合排列成各种不同的形状,堆叠成不同个体。
对于每一个组件的内部来说,维持组件的内部运行就需要状态(state)。当组件初始化的时候,相关的数据会存放在state中;当更新界面时,对应的数据会发生变化,也即state内的数据发生变化。当state发生变化时,就会触发虚拟DOM的刷新,而虚拟DOM的刷新机制如前面所说的,通过比较Diff确定差异部分。进而触发了真实DOM差异部分的刷新。
总结成一句话:State主要用于维护组件内部的数据,当state发生变化,就触发界面刷新。
关于State,还有一些操作,如初始化,更新状态。下面分别举例说明。
1.2.初始化State
一般地,State的初始化,若是类组件的话,在类的构造函数内(contrutor);函数组件就一般是以属性的方式初始化。
//类的构造函数
import React, { PureComponent } from 'react'
export default class Person extends PureComponent {
constructor(props){
super(props)
this.state = {name:"Jom",age:"18"}
}
render() {
return (
<div>
Hello,{this.state.name}
Your age is:{this.state.age}
</div>
)
}
}
在上面的代码中,我们初始化了name,age两个state值。一旦state初始化完成,就可以在render中进行访问。
然后在终端上运行npm run start命令,在浏览器中查运行结果。
若是在VSCode上执行上述命令,就会打开浏览器并加载测试页面。
注:执行上述命令,需要进入到工程目录下,否则会报错误.如下图所示。
当然,我们也可以以属性的方式来对state赋值。如下例子
export default class Person extends PureComponent {
state = {name:"Jom",age:"18"}
render() {
return (
<div>
Hello,{this.state.name}
Your age is:{this.state.age}
</div>
)
}
}
这个的好处就是不用写构造函数(constructor)了。
1.3.修改State
想要修改state的数据,那是不能随便修改的,React提供了唯一的一个接口来修改State的数据,即setState().
//Person.css 文件
.PersonButton_css {
width: 100px;
height: 40px;
background-color: red;
}
//Person.js文件
import React,{PureComponent} from "react"
import '../CSS/Person.css';
class Person extends PureComponent {
/*constructor(props) {
super(props)
this.state = {name:"Jam",age:18}
}*/
state = {name:"Jam",age:19}
handleUpdateName(){
this.setState({name:"Andy",age:20})
}
// 箭头函数 实现方式
handleUpdateName2 = () => {
this.setState({name:"John",age:23})
}
render() {
return (
<div>
Hello, {this.state.name}
<p> Your age is: {this.state.age}</p>
<button className="PersonButton_css" onClick={this.handleUpdateName.bind(this) }>修改姓名</button>
<p>
<button className="PersonButton_css" onClick={this.handleUpdateName2}>修改姓名2</button>
</p>
</div>
)
}
}
export default Person;
这里需要注意:调用setState方法,并不会马上对数据进行修改。若此时去获取最新数据,一般还是旧的数据。因为setState的内部更新数据的机制是:先把数据放到循环队列中,只有执行到队列的对应位置,才会更新数据。
当然Facebook也提供一个方法来获取更新后数据的方式:带回调函数的setState方法。当更新数据时,就产生一个回调函数,通过此回调,可以准确获取到更新后的数据。
import React,{PureComponent} from "react"
import '../CSS/Person.css';
class Person2 extends PureComponent {
constructor(props) {
super(props)
this.state = {name:"Jom",age:18}
}
handleUpdateName(){
this.setState({name:"Andy",age:20},() => {
console.log("State 更新成功")
})
}
// 箭头函数 实现方式
handleUpdateName2 = () => {
this.setState({name:"John",age:23},()=> {
console.log("State 更新成功2")
})
}
render() {
return (
<div>
Hello, {this.state.name}
<p> Your age is: {this.state.age}</p>
<button className="PersonButton_css" onClick={this.handleUpdateName.bind(this) }>修改姓名(带回调方式)</button>
<p>
<button className="PersonButton_css" onClick={this.handleUpdateName2}>修改姓名2(带回调方式)</button>
</p>
</div>
)
}
}
export default Person2;
最后,还得说明下,setState是浅拷贝,如果要拷贝结构比较深的对象,就会出错。建议换成JSON.string()进行深拷贝。
2.props
前面说的一样,React以组件形式组合而成。那么组件间如何通讯呢?Facebook提供了props方案来解决这个问题,即使用props来实现组件间的数据通讯。
2.1 接收并渲染props值
先创建一个是接收数据的组件
import React,{PureComponent} from "react"
import '../CSS/Person.css';
class Person3 extends PureComponent {
constructor(props) {
super(props)
console.log(this.props)
}
render() {
var {name,age} = this.props
return (
<div>
Hello, {name}
<p> Your age is: {age}</p>
</div>
)
}
}
export default Person3;
另一个是发送数据的js页面。
import Person3 from './Components/Person3.js';
function App() {
return (
<div className="App">
{
/*<Person/>
<Person2/> */
}
<Person3 name="Andy2" age="26"></Person3>
</div>
);
}
总结:
props是比较简单的.一端负责传递数据,一端负责接收数据。
发送端,传递数据,像html标签加属性一样,如<Person3 name="John"/>一样,这样组件内部可以通过props读取到name值了。
接收端,在构造函数内接收并打印props的数据,就知道props传递来什么值,具体使用主要在rander函数中。
这里有点鸡肋的地方:props只适用于父组件与子组件的通讯,不适合于兄弟及其祖孙组件间通讯。要解决兄弟及其祖孙的通讯,就需要使用reduex了。
3.ref
ref类似于html标签中的id标签,标识唯一组件,这样我们在全局模块中,可以通过ref标识来唯一确定组件名,并获取组件相关数据。例如下面的例子中id。
<div><button id=“myButton">按钮</button></div>
<div><MyComponent ref=“myButton">按钮</MyComponent></div>
创建ref的三种方式
3.1.String ref(绑定字符串方式)
通过this.refs.绑定的ref的名字获取到节点dom。举例如下
import React from "react"
import '../CSS/Person.css';
class MyInputText extends React.Component{
handleSubmit = () => {
console.log(`姓名:${this.nameInput.value}`);
console.log("学校 " + this.refs.schoolInput.value);
}
render(){
return(
<form>
<input type="text" ref={(nameInput) => {nameInput.focus();this.nameInput = nameInput}}/>
<br></br>
<input type="text" ref="schoolInput"/><br></br>
<button onClick={this.handleSubmit}>提交</button>
</form>
)
};
}
export default MyInputText;
这种方式已经不被最新版的react推荐使用。继续使用,有如下图的错误.
2.callblock ref(函数回调方式) (该实践来自网络)
在class中声明函数,在函数中绑定ref。使用这种方法可以将子组件暴露给父组件以使得父组件能够调用子组件的方法。
通过函数的方法绑定ref可以将整个子组件暴露给父组件
3.createRefAPI ref
通过使用React.createRef()方法,来创建对应Ref变量,将这些变量绑定到对应标签的ref中,这样该变量的current则指向绑定的标签了。举例说明.
import React from "react";
import '../CSS/Person.css';
class Person5 extends React.Component {
inputRef = React.createRef();
handleUpdateName2 = () => {
this.inputRef.current.focus();
console.log("focus...")
}
render() {
return(
<div>
<input type="text" ref={this.inputRef}/><br></br>
<button className="PersonButton_css" onClick={this.handleUpdateName2}>提交</button>
</div>
)
}
}
export default Person5;
最后再说明下,react并不推荐过度使用ref,因为过度使用ref会带来性能上的问题,所以我们在使用过程中尽量优先考虑state来完成对应的任务。这样,就符合React给我们推荐的--数据驱动的思想。