# React

什么是 React?

React 是一个用于构建用户界面的 JavaScript 库。React 主要用于构建 UI

为什么学习 React?

它引入了一种新的方式来处理浏览器 DOM。那些需要手动更新 DOM、费力地记录每一个状态的日子一去不复返了 —— 这种老旧的方式既不具备扩展性,又很难加入新的功能,就算可以,也是有着冒着很大的风险。React 使用很新颖的方式解决了这些问题。你只需要声明地定义各个时间点的用户界面,而无序关系在数据变化时,需要更新哪一部分 DOM。在任何时间点,React 都能以最小的 DOM 修改来更新整个应用程序。

特点如下

  1. 采用组件化模式、声明式编码,提高了开发效率以及组件复用率;
  2. 在 React Native 中可以使用 React 语法进行 移动端开发
  3. 使用 虚拟DOM + Diffing 算法,尽量减少与真实 DOM 的交互;

前置 JS 知识

判断 this 指向,class 类,ES6,npm 包,原型、原型链,数组操作,模块化

# jsx

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>hello react</title>
</head>
<body>
<!--准备好一个“容器”-->
<div id="demo">?</div>
<!--引入核心库-->
<script src="https://cdn.bootcss.com/react/16.4.0/umd/react.development.js"></script>
<!-- 引入react-dom 用于支持react操作dom -->
<script src="https://cdn.bootcss.com/react-dom/16.4.0/umd/react-dom.development.js"></script>
<!-- 引入babel 把jsx转为js -->
<script src="https://cdn.bootcss.com/babel-standalone/6.26.0/babel.min.js"></script>

<script type="text/babel">
//1.创建虚拟DOM
const VDOM = <h1>Hello World</h1>;
//2.渲染虚拟DOM到页面
ReactDOM.render(VDOM, document.getElementById("demo"));
</script>
</body>
</html>

需要注意的点

  1. <script> 标签内要写 ‘text/babel’

  2. const VDOM = <h1>hello…<h1> 这里不要加 “” 或者 ‘’

为什么 React 要求用 JSX 而不是 JS ??

如果需要用到标签嵌套,jsx 更方便,而 js 创建虚拟 dom 会非常繁琐

# 真实 DOM 和虚拟 DOM

image-20221130104457572

关于虚拟 DOM

​ 1. 本质上是 Object 对象

​ 2. 虚拟 DOM 比较 "轻", 真实 DOM 比较 "重", 因为虚拟 DOM 是 React 内部在用,无需真实 DOM 上那么多属性

3. 虚拟DOM最终会被React转化为真实DOM,呈现在页面上

# JSX 语法规则

  • ​ 读取数据要用
  • ​ 读取 class 属性要用 className=xxx 而非 class= ‘’
  • ​ 添加内联样式 style=<!–swig21–>, 即第一个 {} 内写一个样式对象 {}
  • ​ 虚拟 DOM 只能有一个根标签
  • ​ 标签必须闭合 或者
  • ​ 标签渲染
    • 1. 若标签首字母小写,则将标签转为 html 中对应的同名标签,若 html 无对应标签,则报错;
    • 2. 若首字母大写,则 react 会渲染对应的组件,若没有,则报错;

# 组件化

# 安装插件

React Developer Tools
image-20230205142139780

# 函数式组件

适用于简单组件 (无状态)

       1. -组件- 首字母必须大写
       1. 函数内的this会指向 undefined
1
2
3
4
5
6
7
8
<script type="text/babel">
function Demo(){
console.log(this); //这里的this会指向undefined 因为babel编译按照严格模式 禁止this指向window
return <h1>我是函数式定义组件</h1>
}
//渲染到页面
ReactDOM.render(<Demo/>,document.getElementById("jsx"));
</script>

安装插件后,可以在开发者工具 查看组件

image-20230205145004663

# 关于 react 元素调用函数

原生有三种

1
2
3
4
5
6
7
8
9
// 一
const dom = document.getElementById('btn')
dom.addEventListener('click',()=>{...})
// 二
const dom = document.getElementById('btn')
dom.onclick= () = >{...}
// 三
<button onclick="demo()"/>
function demo(){...}

React 中尽量使用第三种,并且需要注意, onClick 而不是 onclick ,要传入函数 {f} 而不是函数的调用

1
2
3
4
5
// react中
render(){
return <h1 onClick={demo}>确认</h1>
// demo()表示调用demo函数的结果 这里必须是{demo}
}

# 类式组件

适用于复杂组件 (有状态)

# 类的基础

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
// 创建一个人类
class Person {
name;
age;
constructor(name, age) {
this.name = name;
this.age = age;
}

speak() {
//哪个实例对象调用speak this就指向哪个实例对象
console.log(`我叫${this.name},今年${this.age}岁`);
}
}

// 创建Person实例化对象
const p1 = new Person("小明", 18);
const p2 = new Person("张三", 20);

p1.speak(); // 我叫小明,今年18岁
p2.speak(); // 我叫张三,今年20岁

p1.speak.call(new Person("王五", 19)); //this指向是call的对象
// 我叫王五,今年19岁
//创建一个学生类 继承 Person
class Student extends Person {
constructor(name, age, grade) {
super(name, age); // super
this.grade = grade;
}
// 学生有study方法
study(){
console.log(`I am studying.. in ${this.grade}`)
}
}
const s1 = new Student("老六",16,"高一");
s1.speak(); // 我叫老六,今年16岁
s1.study(); // I am studying.. in 高一
console.log(p1, p2 ,s1);
/*
Person {name: '小明', age: 18} person的方法都是放在了他的原型对象上 👇👇
Person {name: '张三', age: 20} 原型链: person --> Object(构造自Person 有各种属性和speak()方法)--> Object(顶层对象)
Student {name: '老六', age: 16, grade: '高三'} student --[Prototype]--> person(构造自Student) --[Prototype]--> object(构造自Person)
*/

类中所定义的方法都是放在了类的原型对象上

  • 原型 (prototype) 是函数的一个特殊属性,即指针指向 原型对象
  • 原型对象 (prototype object) 是一个属于其所在 函数的空对象 ,可以添加属性和方法。其自身 constructor 属性指向其函数
image-20230205152741457

image-20230205152727659

​ 每个函数都有一个 prototype 属性。所有的 JavaScript 对象都会从对应 prototype(原型对象)中继承属性和方法.

如上图:
speak () 方法是放在原型对象里的
Person (小明…) 是 实例
第一个 Object 是 原型对象
Person (name,age) 是 构造函数

# 创建类式组件

1
2
3
4
5
6
7
class Demo extends React.Component {		// 一定要继承 React.Component
render() { // 一定要有 render()
return <h1>我是类式定义组件,用于复杂组件</h1>
}
}

ReactDOM.render(<Demo />, document.getElementById("jsx"))

# 组件的 this

在组件内创建自定义方法时,this 为 undefined,如何解决?

  1. 强制绑定给实例对象,使用 bind ()
1
this.tick = this.tick.bind(this)
  1. 使用箭头函数
1
tick = () =>{xxx}

# 组件三大属性

# state

当组件中的一些数据在某些时刻发生变化时,这时就需要使用 state 来跟踪状态。

stateprops 之间最重要的区别是: props 由父组件传入,而 state 由组件本身管理。组件不能修改 props ,但它可以修改 state

​ 对于所有变化数据中的每个特定部分,只应该由一个组件在其 state 中 “持有” 它。不要试图同步来自于两个不同组件的 state。相反,应当将其提升到最近的共同祖先组件中,并将这个 state 作为 props 传递到两个子组件。

看一个来自官方文档的 [时钟案例](Hello World in React (codepen.io))

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// 创建一个类组件 基础的时钟
class Clock extends React.Component {
constructor(props) {
super(props);
this.state = { date: new Date() }; //存储着时间
}

//指针方法 负责更新state存的时间
tick() {
this.setState({ date: new Date() });
}

// 挂载后开启计时,每秒更新一下tick
componentDidMount() {
this.timer = setInterval(() => this.tick(), 1000);
}

// 卸载后清除定时器
componentWillUnmount() {
clearInterval(this.timer);
}

render() {
//render用来return的
return (
<div>
<h2>现在时间</h2>
<h2>It is {this.state.date.toLocaleTimeString()} now.</h2>
</div>
);
}
}

ReactDOM.render(<Clock />, document.getElementById("jsx"));

让我们来快速概括一下发生了什么和这些方法的调用顺序:

  1. <Clock /> 被传给 ReactDOM.render() 的时候,React 会调用 Clock 组件的构造函数。因为 Clock 需要显示当前的时间,所以它会用一个包含当前时间的对象来初始化 this.state 。我们会在之后更新 state。
  2. 之后 React 会调用组件的 render() 方法。这就是 React 确定该在页面上展示什么的方式。然后 React 更新 DOM 来匹配 Clock 渲染的输出。
  3. Clock 的输出被插入到 DOM 中后,React 就会调用 ComponentDidMount() 生命周期方法。在这个方法中, Clock 组件向浏览器请求设置一个计时器来每秒调用一次组件的 tick() 方法。
  4. 浏览器每秒都会调用一次 tick() 方法。 在这方法之中, Clock 组件会通过调用 setState() 来计划进行一次 UI 更新。得益于 setState() 的调用,React 能够知道 state 已经改变了,然后会重新调用 render() 方法来确定页面上该显示什么。这一次, render() 方法中的 this.state.date 就不一样了,如此以来就会渲染输出更新过的时间。React 也会相应的更新 DOM。
  5. 一旦 Clock 组件从 DOM 中被移除,React 就会调用 componentWillUnmount() 生命周期方法,这样计时器就停止了。

# props

​ 当 React 元素为用户自定义组件时,它会将 JSX 所接收的属性(attributes)以及子组件(children)转换为单个对象传递给组件,这个对象被称之为 “props”。

props 传值语法糖

展开运算符

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
         const p1 = {name: 'Tom',age: 17,sex: '女'}
const p2 = {name: 'Jack',age: 18}
function PersonListComponent(props){
return (
<ul>
<li>姓名:{props.name}</li>
<li>性别:{(props.sex)?props.sex:'男'}</li> {// 设置默认为男}
<li>年龄:{props.age}</li>
</ul>
)
}
function App(){
return(
<div>
<PersonListComponent {...p1} />
<PersonListComponent {...p2} />
</div>
);
}
ReactDOM.render(<App/>,document.getElementById("jsx"))

# refs

refs 实际上用途就是把节点 node 本身传到实例对象 obj 里面使用,一般需要通过一个元素访问另一个元素时使用 ref。

  1. 字符串 ref
1
2
3
 <h2 ref="title">{msg}</h2>
// 接收[组件]的refs属性下的title
console.log(this.refs.title); // component.refs.title
  1. 内联 ref
1
2
<h2 ref={el => this.title = el}>{msg}</h2>
// 把节点存在[组件]的title属性中 component.title
  1. 回调 ref
1
2
3
<h2 ref={title}>{msg}</h2>
title=(e)=>{ this.title = e }
// 把节点存在[组件]的title属性中 component.title
  1. 创建 ref
1
2
3
<h2 ref={myRef}>{msg}</h2>
myRef = React.createRef(); // 把节点存在 component.myRef 对象 {current: <h2>}
// conponent.myRef.current = <h2>
# 字符串 refs (即将过时)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Demo extends React.Component {

showData=()=>{
console.log(this)
alert(this.refs.input1.value)
}

showData2=()=>{
console.log(this.refs.input2)
const {value} = this.refs.input2
alert(value)
}

render() {
return(
<div>
<input ref="input1" type="text" />
<button onClick={this.showData}>点击显示左侧数据</button>
<input ref="input2" onBlur={this.showData2} placeholder="失去焦点显示数据" type="text" />
</div>
)
}
}
ReactDOM.render(<Demo />, document.getElementById('jsx'))

字符串形式的 ref 中,ref 属性传值会生成标签的节点存储在实例对象 Demo的refs属性中

image-20230206101104504


# 回调形式 refs (推荐)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Demo extends React.Component {
showData = () => {
alert(this.input1.value);
console.log(this) // this指向 Demo实例对象
};

showData2 = () => {
const { value } = this.input2;
alert(value);
};

render() {
return (
<div>
<input ref={(e)=>{this.input1=e}} type='text' />
<button onClick={this.showData}>点击显示左侧数据</button>
<input ref={e=>this.input2=e} onBlur={this.showData2} placeholder='失去焦点显示数据' type='text' />
</div>
);
}
}
ReactDOM.render(<Demo />, document.getElementById("jsx"));

回调形式的 ref 中,ref 传数据不会存储在实例对象 Demo的refs属性中 ,而是 直接挂载为实例对象Demo的属性

image-20230206101333608

特别注意官网提到的 回调 ref 执行次数的问题

​ 如果 ref 回调函数是以内联函数的方式定义的,在更新过程中它会被 执行两次第一次传入参数 null ,然后第二次会传入参数 DOM 元素。这是因为在每次渲染时会创建一个 新的函数实例 ,所以 React 清空旧的 ref 并且设置新的。通过将 ref 的回调函数定义成 class 的绑定函数的方式可以避免上述问题,但是大多数情况下它是无关紧要的

class 绑定函数的方式

1
2
3
4
5
6
7
8
9
10
 <input ref={(e)=>{this.input1=e}} type='text' />
// 改为
<input ref={this.show} type='text' />
// 再在class里面创建一个函数绑定
..Demo{
show=(e)=>{
this.input1=e
alert(e.value)
}
}
# 创建 Refs (最推荐,v16 以上)
  • ref 属性用于 HTML 元素时,构造函数中使用 React.createRef() 创建的 ref 接收底层 DOM 元素作为其 current 属性。
1
2
3
4
5
6
7
8
9
10
class MyComponent extends React.Component {
myRef = React.createRefs()
showData = () => {
console.log(this.myRef) {// this.myRef = {current: value}}
alert(this.myRef.current.value)
};
render() {
return <div ref={this.myRef} />;
}
}

其实仔细读读官方文档发现写得挺好的,那就先写到这里了,跟着官方文档慢慢看吧。

# 表单

一定要学一下 formik

# 非受控组件

​ 由 ref 携带表单 dom 然后获取 value 数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//非受控组件 现用现取
class Login extends React.Component {
handleSubmit = (e) => {
e.preventDefault(); //阻止表单提交
alert(`账号是${this.username.value}密码是${this.password.value}`); //模拟使用ajax无刷新传值
};
render() {
return (
<div>
<form onSubmit={this.handleSubmit}>
<input ref={(c) => (this.username = c)} name='username' type='text' />
<input ref={(c) => (this.password = c)} name='password' type='password' />
<button>登陆</button>
</form>
</div>
);
}
}

# 受控组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Login extends React.Component {
state = {username: '', password: ''};
handleSubmit = (e) => {
e.preventDefault(); //阻止表单提交
alert(`账户名是${this.state.username}密码是${this.state.password}`)
};
saveUsername=(e)=>{
this.setState({username: e.target.value});
}
savePassword=(e)=>{
this.setState({password: e.target.value});
}
render() {
return (
<div>
<form onSubmit={this.handleSubmit}>
<input onChange={this.saveUsername} name='username' type='text' />
<input onChange={this.savePassword} name='password' type='password' /> {//利用 onChange事件实现数据驱动的单向绑定}
<button>登陆</button>
</form>
</div>
);
}
}

# 优化

​ 以上例子中 saveUsername,savePassword 方法严重重复,如果还需要输入其他数据,就会变得很繁琐,将以上代码优化为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class Login extends React.Component {
state = {username: '', password: ''};
handleSubmit = (e) => {
e.preventDefault(); //阻止表单提交
};

//优化为一个函数 采用es6的计算属性名称语法
saveData=(event)=>{
const target = event.target
const name = target.name
this.setState({
[name]: target.value
})
}
render() {
return (
<div>
<form onSubmit={this.handleSubmit}>
<label htmlFor='username'>用户名:</label>
<input onChange={this.saveData} name='username' type='text' />
<label htmlFor='password'>密码:</label>
<input onChange={this.saveData} name='password' type='password' />
<button>登陆</button>
</form>
</div>
);
}
}

# 组件生命周期

生命周期详细介绍参考 深入详解 React 生命周期

在之前的时钟案例中用到了 componentDidMount()componentWillUnmount() 就是生命周期函数.

组件将要挂载时触发的函数:componentWillMount
组件挂载完成时触发的函数:componentDidMount
是否要更新数据时触发的函数:shouldComponentUpdate 不写默认返回 true 写了该函数不写返回值 则默认返回 undefined
将要更新数据时触发的函数:componentWillUpdate
数据更新完成时触发的函数:componentDidUpdate
组件将要销毁时触发的函数:componentWillUnmount
父组件中改变了 props 传值时触发的函数:componentWillReceiveProps 第一次加载不算,是接收 new props 才会算

React 旧版生命周期

旧版生命周期 image-20230206154029331

新版生命周期

新版
更新于 阅读次数

请我喝[茶]~( ̄▽ ̄)~*

寂林 微信支付

微信支付

寂林 支付宝

支付宝