Appearance
前言
要想摩托跑得快,螺丝必须得拧紧。说的就是我这种野鸡,只顾着往前跑,切记穿鞋了。跑到最后,裁判说你没穿鞋,成绩不算,功亏一篑
今天咱就把三颗重要螺丝再拧一拧
在React开发反而过程中,出现最多的无非就是
state、porps、ref
三大属性
因为卷的原因,我这次用 react+vite+TypeScript
创建一个项目出来把三大属性过一遍,一方面加深TS的运用,另一方面加深React的认识,也算是总结吧。
组件实例三大属性 — state
定义:这个就相当于一个存储数据的对象。
如果用过vue的伙伴都知道,vue里面存储当前实例的数据是data,而这个data官方建议是个函数而不是对象,有兴趣可以去了解一下,个人总结的原因就是每个.vue都是一个单独的实例,如果使用对象,这个会变成全局性的,会影响到其他实例的data数据,而函数有作用域并不会影响到。
写法一:
ts
import React, { Component } from "react";
interface stateType {
isHot: boolean;
wind: string;
}
export default class App extends Component<{}, stateType> {
constructor(props: any) {
super(props);
//初始化状态
this.state = { isHot: false, wind: "微风" };
this.changeWeather = this.changeWeather.bind(this);
}
changeWeather() {
console.log("changeWeather",this);
//获取原来的isHot值
const isHot = this.state.isHot;
this.setState({ isHot: !isHot });
}
render() {
console.log("render");
const { isHot, wind } = this.state;
return (
<h1 onClick={this.changeWeather}>
今天天气很{isHot ? "炎热" : "凉爽"},{wind}
</h1>
);
}
}
import React, { Component } from "react";
interface stateType {
isHot: boolean;
wind: string;
}
export default class App extends Component<{}, stateType> {
constructor(props: any) {
super(props);
//初始化状态
this.state = { isHot: false, wind: "微风" };
this.changeWeather = this.changeWeather.bind(this);
}
changeWeather() {
console.log("changeWeather",this);
//获取原来的isHot值
const isHot = this.state.isHot;
this.setState({ isHot: !isHot });
}
render() {
console.log("render");
const { isHot, wind } = this.state;
return (
<h1 onClick={this.changeWeather}>
今天天气很{isHot ? "炎热" : "凉爽"},{wind}
</h1>
);
}
}
这里有几个知识点,我们来梳理一下,
1、为什么Component<{}, stateType>
这样的写法?
如果熟悉ts的同学就知道这是个类型断言,stateType
是个自定义的接口,那为什么有两个参数在里面呢?很简单,让我们来看一下React源码定义的这个Component
可以看到我们的 Component被定义成Component<P,S>
,其中的P
和S
都是个泛型,再看看state
被定义为Readonly<S>
,意思是:state的所有属性设置为只读的类型。props被定义为Readonly<P> | P
,意思是:props的所有属性设置为只读的类型 或者是 P
类型
既然都是泛型,那么类型肯定要有自己来定义了,所以才会出现这样的写法Component<{}, stateType>
2、为什么使用bind
改变changeWeather
的this指向呢,也就是我们代码中的this.changeWeather = this.changeWeather.bind(this)
?
写过ES6的class类的伙伴们都知道,一般在class类里面定义的方法,在没有被属性的修饰符修饰前,changeWeather
放在App
的原型对象上,提供给实例使用。
ES6 class的修饰符(具体详情查看阮一峰老师的ES6讲解):
- 静态属性和静态方法在前面添加
static
, - 私有属性在前面添加
#
- 私有方法用下划线表示,但是在外部还可以被访问到不保险
TS class的修饰符(具体可以查看英文TS官网对修饰符的解释):
public
修饰符---公共的,类中成员默认的修饰符,代表的是公共的,任何位置都可以访问类中的成员private
修饰符---私有的,类中的成员如果使用private来修饰,那么外部是无法访问这个成员数据的,当然,子类中也是无法访问该成员数据的protected
修饰符---受保护的,类中的成员如果使用protected来修饰,那么外部是无法访问这个成员数据的,当然,子类中是可以访问该成员数据的#
修饰符---表示私有字段,有时我们称之为私有名称; 每个私有字段名称都唯一地限定于其包含的类; 不能在私有字段上使用 TypeScript 可访问性修饰符(如 public 或 private); 私有字段不能在包含的类之外访问,甚至不能被检测到readonly
修饰符---只读不能写static
修饰符---通过static
修饰的成员叫静态成员,静态成员无需实例化,直接通过类名调用
由于changeWeather是作为onClick的回调,所以不是通过实例调用的,是直接调用,类中的方法默认开启了局部的严格模式,所以changeWeather中的this为undefined,我们可以看这个例子:
js
function aa(){
console.log(this)
}
aa()
function bb(){
"use strict";
console.log(this)
}
bb()
function aa(){
console.log(this)
}
aa()
function bb(){
"use strict";
console.log(this)
}
bb()
打印的结果:
如果熟悉this指向的同学一看就知道其中的原因所在,我也给大家总结一下,这里先不做详细的介绍:
- 在严格模式下,在全局作用域中,this指向window对象。
- 在严格模式下,全局作用域中函数中的this等于undefined。不是严格模式时,这里的this指向window。在react中render()里的this是指向组件的实例对象。注意区分是render()里的this还是函数中的this。(这两个this指向不同,所以没法在全局作用域的函数中直接调用this取值)
- 在严格模式下,对象的函数中的this指向调用函数的对象实例
- 在严格模式下,构造函数中的this指向构造函数创建的对象实例。
- 在严格模式下,在事件处理函数中,this指向触发事件的目标对象。
所以最后你知道了为什么要改变changeWeather
中的this
指向了吗?
总结了上面的知识,我们来简写一下写法吧
写法二:
ts
import React, { Component } from "react";
interface stateType {
isHot: boolean;
wind: string;
}
export default class example1 extends Component<{}, stateType> {
state = {
isHot: false,
wind: "微风",
};
changeWeather = () => {
const isHot = this.state.isHot;
this.setState({ isHot: !isHot });
};
render() {
const { isHot, wind } = this.state;
return (
<h1 onClick={this.changeWeather}>
今天天气很{isHot ? "炎热" : "凉爽"},{wind}
</h1>
);
}
}
import React, { Component } from "react";
interface stateType {
isHot: boolean;
wind: string;
}
export default class example1 extends Component<{}, stateType> {
state = {
isHot: false,
wind: "微风",
};
changeWeather = () => {
const isHot = this.state.isHot;
this.setState({ isHot: !isHot });
};
render() {
const { isHot, wind } = this.state;
return (
<h1 onClick={this.changeWeather}>
今天天气很{isHot ? "炎热" : "凉爽"},{wind}
</h1>
);
}
}
这个写法是不是不要比写法一精简很多了。
1、为什么有了箭头函数后就不用改变changeWeather
的this
指向呢,我们来认识一下箭头函数与普通函数的区别:
一、箭头函数不会创建自己的this(重要!!深入理解!!),也就是说他没有自己的this,有兴趣的可以去看一下MDN对箭头函数的this的解释
二、箭头函数继承而来的this指向永远不变(重要!!深入理解!!)
三、.call()/.apply()/.bind()无法改变箭头函数中this的指向
四、箭头函数不能作为构造函数使用,也就是说他不能new。我们先了解一下构造函数的new都做了些什么?简单来说,分为四步:
① JS内部首先会先生成一个对象;
② 再把函数中的this指向该对象;
③ 然后执行构造函数中的语句;
④ 最终返回该对象实例。
但是!!因为箭头函数没有自己的this
,它的this
其实是继承了外层执行环境中的this
,且this
指向永远不会随在哪里调用、被谁调用而改变,所以箭头函数不能作为构造函数使用,或者说构造函数不能定义成箭头函数,否则用new
调用时会报错!
js
let Fun = (name, age) => {
this.name = name;
this.age = age;
};
// 报错
let p = new Fun('cao', 24);
let Fun = (name, age) => {
this.name = name;
this.age = age;
};
// 报错
let p = new Fun('cao', 24);
五、箭头函数没有自己的arguments。在箭头函数中访问arguments
实际上获得的是外层局部(函数)执行环境中的值。
js
// 例子一
let fun = (val) => {
console.log(val); // 111
// 下面一行会报错
// Uncaught ReferenceError: arguments is not defined
// 因为外层全局环境没有arguments对象
console.log(arguments);
};
fun(111);
// 例子二
function outer(val1, val2) {
let argOut = arguments;
console.log(argOut); // ①
let fun = () => {
let argIn = arguments;
console.log(argIn); // ②
console.log(argOut === argIn); // ③
};
fun();
}
outer(111, 222);
// 例子一
let fun = (val) => {
console.log(val); // 111
// 下面一行会报错
// Uncaught ReferenceError: arguments is not defined
// 因为外层全局环境没有arguments对象
console.log(arguments);
};
fun(111);
// 例子二
function outer(val1, val2) {
let argOut = arguments;
console.log(argOut); // ①
let fun = () => {
let argIn = arguments;
console.log(argIn); // ②
console.log(argOut === argIn); // ③
};
fun();
}
outer(111, 222);
上面例子二,①②③处的输出结果如下:
很明显,普通函数
outer
内部的箭头函数fun
中的arguments
对象,其实是沿作用域链向上访问的外层outer
函数的arguments
对象。
可以在箭头函数中使用rest参数代替arguments对象,来访问箭头函数的参数列表!!
六、箭头函数没有原型prototype
七、箭头函数不能用作Generator函数,不能使用yeild关键字
单单一个state
就能让我学习到class类的修饰符、this指向,箭头函数、泛型、类型断言、泛型工具、联合类型,真是不总结都知道害怕,我已卷到这么多了。
组件实例三大属性 — props
定义:在父组件给子组件传递参数时的桥梁作用,相当于vue的props传值一样的效果,但是却没有vue用起来那么麻烦,纯属个人感受。
写法:
ts
import React, { Component } from "react";
interface propsType {
name: string;
age: number;
sex: "男" | "女";
}
class Index1 extends Component<propsType> {
render(): React.ReactNode {
const { name, age, sex } = this.props;
return (
<ul>
<li>姓名:{name}</li>
<li>性别:{sex}</li>
<li>年龄:{age}</li>
</ul>
);
}
}
export default class example2 extends Component {
render() {
let p:propsType = {
name:"李青霞",
age:20,
sex:"女"
}
return (
<>
<h1>Props基本使用</h1>
<Index1 name="周星星" age={18} sex="男"></Index1>
<Index1 name="朱茵" age={19} sex="女"></Index1>
<Index1 {...p}></Index1>
</>
);
}
}
import React, { Component } from "react";
interface propsType {
name: string;
age: number;
sex: "男" | "女";
}
class Index1 extends Component<propsType> {
render(): React.ReactNode {
const { name, age, sex } = this.props;
return (
<ul>
<li>姓名:{name}</li>
<li>性别:{sex}</li>
<li>年龄:{age}</li>
</ul>
);
}
}
export default class example2 extends Component {
render() {
let p:propsType = {
name:"李青霞",
age:20,
sex:"女"
}
return (
<>
<h1>Props基本使用</h1>
<Index1 name="周星星" age={18} sex="男"></Index1>
<Index1 name="朱茵" age={19} sex="女"></Index1>
<Index1 {...p}></Index1>
</>
);
}
}
从上面就可以看出,React的props传值就很有意思,直接写在标签内,还可以解构的形式,是不是写起来比vue方便很多。
我们在vue中使用prop传值时,会有一个类型的定义,是否必传,还可以写校验规则的,具体可以看一下vue对Prop验证的解释
我们的React中也可以,但是只不过要借助另外一个库叫prop-types
,具体的配置项可以看一下React对PropsType的解释。
ts
import React, { Component } from "react";
import PropTypes from "prop-types";
interface propsType {
name: string;
age: number;
sex: "男" | "女";
speak?:Function
}
function speak() {
console.log("我说话了");
}
class Index1 extends Component<propsType> {
static propTypes: { name: PropTypes.Validator<string>; sex: PropTypes.Requireable<string>; age: PropTypes.Requireable<number>; speak: PropTypes.Requireable<(...args: any[]) => any> };
static defaultProps: {
sex: string; //sex默认值为男
age: number; //age默认值为18
name: string;
};
render(): React.ReactNode {
const { name, age, sex,speak } = this.props;
if(speak){
speak!()
console.log("是谁在说话",name)
}
return (
<ul>
<li>姓名:{name}</li>
<li>性别:{sex}</li>
<li>年龄:{age}</li>
</ul>
);
}
}
Index1.propTypes = {
name: PropTypes.string.isRequired,
sex: PropTypes.string,
age: PropTypes.number,
speak: PropTypes.func,
};
Index1.defaultProps = {
sex: "男", //sex默认值为男
age: 18, //age默认值为18
name: "李四",
};
export default class example2 extends Component {
render() {
let p: propsType = {
name: "李青霞",
age: 20,
sex: "女",
};
return (
<>
<h1>Props基本使用</h1>
<Index1 name="周星星" age={18} sex="男" speak={speak}></Index1>
<Index1 name="朱茵" age={19} sex="女"></Index1>
<Index1 {...p}></Index1>
<Index1></Index1>
</>
);
}
}
import React, { Component } from "react";
import PropTypes from "prop-types";
interface propsType {
name: string;
age: number;
sex: "男" | "女";
speak?:Function
}
function speak() {
console.log("我说话了");
}
class Index1 extends Component<propsType> {
static propTypes: { name: PropTypes.Validator<string>; sex: PropTypes.Requireable<string>; age: PropTypes.Requireable<number>; speak: PropTypes.Requireable<(...args: any[]) => any> };
static defaultProps: {
sex: string; //sex默认值为男
age: number; //age默认值为18
name: string;
};
render(): React.ReactNode {
const { name, age, sex,speak } = this.props;
if(speak){
speak!()
console.log("是谁在说话",name)
}
return (
<ul>
<li>姓名:{name}</li>
<li>性别:{sex}</li>
<li>年龄:{age}</li>
</ul>
);
}
}
Index1.propTypes = {
name: PropTypes.string.isRequired,
sex: PropTypes.string,
age: PropTypes.number,
speak: PropTypes.func,
};
Index1.defaultProps = {
sex: "男", //sex默认值为男
age: 18, //age默认值为18
name: "李四",
};
export default class example2 extends Component {
render() {
let p: propsType = {
name: "李青霞",
age: 20,
sex: "女",
};
return (
<>
<h1>Props基本使用</h1>
<Index1 name="周星星" age={18} sex="男" speak={speak}></Index1>
<Index1 name="朱茵" age={19} sex="女"></Index1>
<Index1 {...p}></Index1>
<Index1></Index1>
</>
);
}
}
其实在TS这种强类型的语言下,先提前定义好接口类型,也是可以的。
然后我们可以把它简写:
ts
import React, { Component } from "react";
import PropTypes from "prop-types";
interface propsType {
name: string;
age: number;
sex: "男" | "女";
}
function speak() {
console.log("我说话了");
}
class Index1 extends Component<propsType> {
static propTypes: { name: PropTypes.Validator<string>; sex: PropTypes.Requireable<string>; age: PropTypes.Requireable<number>; speak: PropTypes.Requireable<(...args: any[]) => any> } = {
name: PropTypes.string.isRequired,
sex: PropTypes.string,
age: PropTypes.number,
speak: PropTypes.func,
};
static defaultProps: {
sex: string; //sex默认值为男
age: number; //age默认值为18
} = {
sex: "男", //sex默认值为男
age: 18, //age默认值为18
};
render(): React.ReactNode {
const { name, age, sex } = this.props;
return (
<ul>
<li>姓名:{name}</li>
<li>性别:{sex}</li>
<li>年龄:{age}</li>
</ul>
);
}
}
export default class example2 extends Component {
render() {
let p: propsType = {
name: "李青霞",
age: 20,
sex: "女",
};
return (
<>
<h1>Props基本使用</h1>
<Index1 name="周星星" age={18} sex="男"></Index1>
<Index1 name="朱茵" age={19} sex="女"></Index1>
<Index1 {...p}></Index1>
<Index1 name="李四"></Index1>
</>
);
}
}
import React, { Component } from "react";
import PropTypes from "prop-types";
interface propsType {
name: string;
age: number;
sex: "男" | "女";
}
function speak() {
console.log("我说话了");
}
class Index1 extends Component<propsType> {
static propTypes: { name: PropTypes.Validator<string>; sex: PropTypes.Requireable<string>; age: PropTypes.Requireable<number>; speak: PropTypes.Requireable<(...args: any[]) => any> } = {
name: PropTypes.string.isRequired,
sex: PropTypes.string,
age: PropTypes.number,
speak: PropTypes.func,
};
static defaultProps: {
sex: string; //sex默认值为男
age: number; //age默认值为18
} = {
sex: "男", //sex默认值为男
age: 18, //age默认值为18
};
render(): React.ReactNode {
const { name, age, sex } = this.props;
return (
<ul>
<li>姓名:{name}</li>
<li>性别:{sex}</li>
<li>年龄:{age}</li>
</ul>
);
}
}
export default class example2 extends Component {
render() {
let p: propsType = {
name: "李青霞",
age: 20,
sex: "女",
};
return (
<>
<h1>Props基本使用</h1>
<Index1 name="周星星" age={18} sex="男"></Index1>
<Index1 name="朱茵" age={19} sex="女"></Index1>
<Index1 {...p}></Index1>
<Index1 name="李四"></Index1>
</>
);
}
}
函数式组件的写法也差不多的:
js
import PropTypes from "prop-types";
import React from "react";
interface propsType {
name: string;
age: number;
sex: "男" | "女";
}
function Index1(props: propsType) {
const { name, age, sex } = props;
return (
<ul>
<li>姓名:{name}</li>
<li>性别:{sex}</li>
<li>年龄:{age}</li>
</ul>
);
}
Index1.propTypes = {
name: PropTypes.string.isRequired, //限制name必传,且为字符串
sex: PropTypes.string, //限制sex为字符串
age: PropTypes.number, //限制age为数值
};
//指定默认标签属性值
Index1.defaultProps = {
sex: "男", //sex默认值为男
age: 18, //age默认值为18
};
export default function example5() {
return (
<>
<h1>函数组件使用props</h1>
<Index1 name="李四"></Index1>
</>
);
}
import PropTypes from "prop-types";
import React from "react";
interface propsType {
name: string;
age: number;
sex: "男" | "女";
}
function Index1(props: propsType) {
const { name, age, sex } = props;
return (
<ul>
<li>姓名:{name}</li>
<li>性别:{sex}</li>
<li>年龄:{age}</li>
</ul>
);
}
Index1.propTypes = {
name: PropTypes.string.isRequired, //限制name必传,且为字符串
sex: PropTypes.string, //限制sex为字符串
age: PropTypes.number, //限制age为数值
};
//指定默认标签属性值
Index1.defaultProps = {
sex: "男", //sex默认值为男
age: 18, //age默认值为18
};
export default function example5() {
return (
<>
<h1>函数组件使用props</h1>
<Index1 name="李四"></Index1>
</>
);
}
组件实例三大属性 — ref
定义:一般是用来获取DOM或者是DOM值,例如input标签,可以通过绑定拿到input中输入的值
字符串形式的ref
js
import React, { Component } from "react";
export default class example6 extends Component {
showData = ()=>{
//会报错
console.log(this.refs)
}
render() {
return (
<>
<h1>字符串形式的ref</h1>
<input ref="inputRef" type="text" placeholder="请输入"/>
<button onClick={this.showData}>获取ref</button>
</>
);
}
}
import React, { Component } from "react";
export default class example6 extends Component {
showData = ()=>{
//会报错
console.log(this.refs)
}
render() {
return (
<>
<h1>字符串形式的ref</h1>
<input ref="inputRef" type="text" placeholder="请输入"/>
<button onClick={this.showData}>获取ref</button>
</>
);
}
}
这样的写法控制台会报错,官方不建议用这过时的写法,具体可以看React对字符串Ref的解释
回调函数形式的ref
ts
import React, { Component } from "react";
export default class example6 extends Component {
showData = () => {
const { inputRef } = this;
console.log(inputRef?.value);
};
inputRef: HTMLInputElement | null | undefined;
render() {
return (
<>
<h1>回调函数形式的ref</h1>
<input ref={(c) => (this.inputRef = c;console.log('@',c);)} type="text" placeholder="请输入" />
<button onClick={this.showData}>获取ref数据</button>
</>
);
}
}
import React, { Component } from "react";
export default class example6 extends Component {
showData = () => {
const { inputRef } = this;
console.log(inputRef?.value);
};
inputRef: HTMLInputElement | null | undefined;
render() {
return (
<>
<h1>回调函数形式的ref</h1>
<input ref={(c) => (this.inputRef = c;console.log('@',c);)} type="text" placeholder="请输入" />
<button onClick={this.showData}>获取ref数据</button>
</>
);
}
}
这样写就不会报错了,但是会有一个问题,当我render更新时,回调函数会一直调用。
这样写就可以解决这个问题:
ts
import React, { Component } from "react";
export default class example6 extends Component {
state = { isHot: false };
showInfo = () => {
const { inputRef } = this;
alert(inputRef!.value);
};
changeWeather = () => {
//获取原来的状态
const { isHot } = this.state;
//更新状态
this.setState({ isHot: !isHot });
};
saveInput = (c: HTMLInputElement | null | undefined) => {
this.inputRef = c;
console.log("@", c);
};
inputRef: HTMLInputElement | null | undefined;
render() {
const { isHot } = this.state;
return (
<>
<h2>今天天气很{isHot ? "炎热" : "凉爽"}</h2>
<input ref={this.saveInput} type="text" />
<br />
<br />
<button onClick={this.showInfo}>点我提示输入的数据</button>
<button onClick={this.changeWeather}>点我切换天气</button>
</>
);
}
}
import React, { Component } from "react";
export default class example6 extends Component {
state = { isHot: false };
showInfo = () => {
const { inputRef } = this;
alert(inputRef!.value);
};
changeWeather = () => {
//获取原来的状态
const { isHot } = this.state;
//更新状态
this.setState({ isHot: !isHot });
};
saveInput = (c: HTMLInputElement | null | undefined) => {
this.inputRef = c;
console.log("@", c);
};
inputRef: HTMLInputElement | null | undefined;
render() {
const { isHot } = this.state;
return (
<>
<h2>今天天气很{isHot ? "炎热" : "凉爽"}</h2>
<input ref={this.saveInput} type="text" />
<br />
<br />
<button onClick={this.showInfo}>点我提示输入的数据</button>
<button onClick={this.changeWeather}>点我切换天气</button>
</>
);
}
}
createRef 创建ref
这个方法相对于上面的都好用很多,个人觉得
ts
import React, { Component } from "react";
export default class example9 extends Component {
myRef = React.createRef<HTMLInputElement>();
myRef2 = React.createRef<HTMLInputElement>();
//展示左侧输入框的数据
showData = () => {
alert(this.myRef.current!.value);
};
//展示右侧输入框的数据
showData2 = () => {
alert(this.myRef2.current!.value);
};
render() {
return (
<div>
<input ref={this.myRef} type="text" placeholder="点击按钮提示数据" />
<button onClick={this.showData}>点我提示左侧的数据</button>
<input onBlur={this.showData2} ref={this.myRef2} type="text" placeholder="失去焦点提示数据" />
</div>
);
}
}
import React, { Component } from "react";
export default class example9 extends Component {
myRef = React.createRef<HTMLInputElement>();
myRef2 = React.createRef<HTMLInputElement>();
//展示左侧输入框的数据
showData = () => {
alert(this.myRef.current!.value);
};
//展示右侧输入框的数据
showData2 = () => {
alert(this.myRef2.current!.value);
};
render() {
return (
<div>
<input ref={this.myRef} type="text" placeholder="点击按钮提示数据" />
<button onClick={this.showData}>点我提示左侧的数据</button>
<input onBlur={this.showData2} ref={this.myRef2} type="text" placeholder="失去焦点提示数据" />
</div>
);
}
}
总结
React中的state、props、ref三颗螺丝我已经再次拧了一下,顺便也拧了拧class类的修饰符、this指向,箭头函数、泛型、类型断言、泛型工具、联合类型这几颗小螺丝。准备发车了,伙伴们准备好了嘛
今天特意设置了一个提醒,提醒今天是周四要发学习分享了