banner
Hi my new friend!

React

Scroll down

学习React

  • 什么是React?
    用于构建用户界面的JS库
    react只关注视图(也就是页面)
    AIA:是一个将数据渲染为HTML视图的开源JS库,感觉像是程序员和DOM之间的代理
  • 步骤
  1. 发送请求获取数据
  2. 处理数据(过滤、整理格式)
  3. 操作DOM呈现页面
  • 谁开发的?
    由Facebook开发,且开源
    软件工程师Jorda Walke创建
  • 为什么用react
  1. 原生js操作DOM繁琐、效率低
  2. 使用JS直接操作DOM, 浏览器会进行大量的重绘重排
  3. 原生没有组件化编码方案,代码复用率低
  • React特点
  1. 采用组件化模式、声明式编码,提高开发效率及组件复用率
  2. 在React Native中可以使用React语法进行移动端开发
  3. 使用虚拟DOM+Diffing算法,减少与真实DOM交互
  • 依赖包
  1. babel.min.js (es6->es5 jsx->js)
  2. react.development.js(react核心库)
  3. react-dom.development(react扩展库)

虚拟DOM

虚拟DOM的两种创建方式

  1. 使用jsx创建虚拟虚拟DOM
  • 格式
    引入react核心库
    引入react-dom,用于支持react操作DOM
    引入babel
1
2
3
4
5
6
7
8
9
10
11
<div id="test"></div>
<div id="demo"></div>

<script type="text/babel">
// 表示里面写jsx
// 1. 创建虚拟DOM
const VDOM = <h1>Hello React</h1> // 此处不要写引号 因为不是字符串
const TDOM = document.getElementById('demo')
// 2. 渲染虚拟DOM到页面
ReactDOM.render(VDOM,document.getElementById("test"))
</script>
  1. 使用js创建虚拟DOM
  • 格式
    引入react核心库
    引入react-dom,用于支持react操作DOM
1
2
3
4
5
6
7
8
9
10
11
12
<div id="test"></div>
<div id="demo"></div>

<script type="text/javascript">
// 表示里面写jsx
// 1. 创建虚拟DOM
const VDOM = React.createlement(标签名,标签属性,标签内容)
const VDOM = React.createlement('h1',{id:'title'},'Hello React')
const TDOM = document.getElementById('demo')
// 2. 渲染虚拟DOM到页面
ReactDOM.render(VDOM,document.getElementById("test"))
</script>

关于虚拟DOM

  1. console.log(VDOM instanceof Object) 本质是 Object 类型的对象
  2. 虚拟DOM比较”轻”,属性少。因为虚拟DOM是react内部在用,无需真实DOM那么多的属性
1
2
3
console.log('虚拟DOM',VDOM)
console.log('真实DOM',TDOM) // 输出<div id="demo"></div>,没法看属性
debugger // 借助断点来看有多少属性
  1. 虚拟DOM最终会被React转化为真实DOM,呈现在页面上

JSX简介

全称:javascript XML
是一个 JavaScript 的语法扩展。在 React 中配合使用 JSX,JSX 可以很好地描述 UI 应该呈现出它应有交互的本质形式。JSX 可能会使人联想到模版语言,但它具有 JavaScript 的全部功能。

为什么使用JSX?

React 认为渲染逻辑本质上与其他UI逻辑内在耦合,比如
上述例子若是在h1标签中再加一个span标签:

1
2
3
4
5
6
7
8
9
// jsx:
const VDOM = (
<h1 id="title">
<span>Hello React</span>
</h1>
)

// js:
const VDOM = React.createlement('h1',{id:'title'},React.createElement('span',{},'Hello React'))

BTW——XML

早期用于存储和传输数据

1
2
3
4
5
6
7
<student>
<name>Tom</name>
<age>19</age>
</student>

<!-- 用json存储 json{}存储对象 js对象存储为json字符串-->
{"name":'Tom',"age":19}

BTW——json

  1. parse方法
  2. stringify方法

jsx语法规则

  1. 定义虚拟DOM时不要写引号
  2. 标签中混入js表达式时用{},myId,myData
    [注意]区分js表达式 和 js语句(代码)
  • 表达式一定会产生一个值,可以放在任何一个需要值的地方
    下面这些都是表达式 :
    .a,
    .a+b
    .demo(1)
    arr.map(() => {return})
    function test() {}
  • 语句(代码)
    下面这些都是语句(代码)
    .if()
    .for()
    .switch() {case:xxx}
  1. 样式的类名指定不要用class,要用className
  2. 内联样式要用,style=两个大括号,里面写key:value的形式 去写
    比如两个大括号,中间写color:’white’
  3. 虚拟DOM,只有一个根标签 在这个例子中所有东西都放h2里
  4. 标签必须闭合
  5. 标签首字母
    (1)若小写字母开头,则将该标签转为html同名元素,若html中无该标签对应同名元素,则报错
    (2)若大写字母开头,react就去渲染对应组件,若组件没有定义,则报错
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<style>
.title {
background:skyblue;
}
</style>

<script type="text/babel">
const myId = 'ALin'
const myData = 'suibian'

// 1. 创建虚拟DOM
const VDOM = (
<h1 id={myId.toLowerCase()} className='title'>
<span>{myData.toLowerCase()}</span>
</h1>
)
// 2. 渲染虚拟DOM
ReactDOM.render(VDOM,document.getElementById('test'))
</script>

例子:渲染数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<script type="text/babel">
const data = ['Angular','React','Vue']
// 1.创建虚拟DOM
const VDOM = (
<div>
<h1>前端js框架列表</h1>
<ul>
{
data.map((item,index) =>{
return <li key={index}>{item}</li>
// 这样写少了key值,可以暂时用index来代替,但最好还是用id,因为index会变化
})
}
</ul>
</div>
)
</script>

模块与组件、模块化与组件化的理解

模块

  • 向外提供特定的js程序,一般就是一个js文件
  • 为什么要拆成模块?
    随着业务逻辑增加,代码越来越多越来越复杂
  • 作用:复用js,提高js运行效率

组件

  • 用来实现局部功能效果的代码和资源集合(html/css/image/js)
  • 为什么要拆成组件?
    一个界面功能更复杂
  • 作用:复用代码,简化项目编码,提高运行效率

模块化

当应用的js都以模块来编写的,这个应用就是一个模块化应用

组件化

当应用是以多组件的方式实现,这个应用就是一个组件化的应用

react面向组件编程

两种定义组件方式

  1. 函数式组件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<div id="test"></div>

<script type="text/babel">
// 1. 创建组函数式件
function Demo() {
console.log(this) // 结果为undefined 因为函数要经过babel翻译,翻译后开启严格模式,禁止自定义函数里的this指向window
return <h2>我是用函数定义的组件(适用于【简单组件】的定义)</h2>
}
// 2. 渲染组件到页面
// ReactDOM.render(demo,document.getElementById('test')) 这样写会报错,不能直接写demo
ReactDOM.render(<Demo/>,document.getElementById('test')) 这样写会报错,不能直接写demo
// D 大写因为是组件,闭合标签,以标签形式写出

/*
执行了ReactDOM.render(<Demo/>)......之后发生了什么?
1. react 解析组件标签,找到Demo组件
2. 发现组件是使用函数定义的,随后调用该函数,将返回的虚拟DOM转为真实的DOM,随后呈现在页面中
*/
</script>
  1. 类式组件

回顾一下es6

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
// 创建一个Person类
class Person {
// 接收new 传递过来的参数,用构造器方法
constructotr(name,age) {
// 构造器中的this指什么?类的实例对象
this.name = name
this.age = age
}
// 除了构造器方法,还有一般方法
speak() {
speak方法放在哪里了? 类的原型对象上
speak方法给谁用的?给实例用
speak中this是谁?p1/p2 通过Person实例调用speak时,speak中的this就是Person实例
console.log(`我叫${this.name},我年龄是${this.age}`)
}
}
// 用类创建一个Person实例对象
const p1 = new Person('tom',19)
const p2 = new Person('jerry',17)
console.log(p1)
console.log(p2)
p1.spaek()
p2.speak()

// 创建一个Student类,继承于Person类
class Student extends Person {
constructor(name,age,grade) { // 自己写构造器,有想添加的东西在这儿写
// 必须使用super,且在最前面
super(name,age)
this.grade = grade
}
// 重写从父类继承过来的方法
speak() {
console.log(`我叫${this.name},我年龄是${this.age},我是${this.grade}年级`)
}
study() {
// study方法放在哪儿? 类的原型上,供实例使用
// 通过Student实例调用study时候,study中的this就是Student的实例
console.log('我很努力的学习')
}
}
const s1 = new Student('小张',15,'高一') // 若想再添加一个年级,但是继承的Person中没有
console.log(s1)
// 静态方法就是构造函数本身属性,不在类的原型对象上,所以实例无法调用

【AIA】

  1. 类中的构造器不是必须写,要对实例进行一些初始化操作,如指定添加属性时才写(比如grade)
  2. 如果A类继承B类,且A类中写了构造器,那么A类构造器中的super是必须要调用的
  3. 类中所定义的方法,都是放在类的原型对象上,供实例使用

创建类式组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<div id="test"></div>
<script type="text/babel">
// 1. 创建类式组件
class MyComponent extends React.Component {
render() {
// render是放在哪里的? 类(MyComponent)的原型对象上,供实例使用
// render中的this是谁?MyComponent的组件实例对象
return <h2>我是用类定义的组件(适用于【复杂组件】的定义)</h2>
}
}
// 2. 渲染组件到页面
ReactDOM.render(<MyComponent/>,document.getELementById('test'))
/*
执行了ReactDOM.render(<MyComponent/>)......之后发生了什么?
1. react 解析组件标签,找到MyComponent组件
2. 发现组件是使用类定义的,随后new出来该类的实例,并通过该实例调用到原型上的render方法
3. 将render返回的虚拟DOM转为真实DOM,随后呈现在页面中
*/
</script>

组件三大属性

第一————组件的状态

  • 像es6一样,我需要用到实例中的state,但天默认是空值,就是空对象。需要自己添加,所以用到constructor和super
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<div id="test"></div>
<script type="text/babel">
// 1. 创建类式组件
class Weather extends React.Component {
constructor(props) {
super(props)
this.state={isHot:true} // 初始化状态
}
render() {
// 读取状态
const {isHot} = this.state
// return <h1>今天天气{this.state.isHot ? '炎热' : '凉爽'}!</h1> 有const声明就不需要写的那么麻烦了
return <h1>今天天气{isHot ? '炎热' : '凉爽'}!</h1>
}
}
// 2. 渲染组件到页面
ReactDOM.render(<Weather/>,document.getELementById('test'))
/*
执行了ReactDOM.render(<MyComponent/>)......之后发生了什么?
1. react 解析组件标签,找到MyComponent组件
2. 发现组件是使用类定义的,随后new出来该类的实例,并通过该实例调用到原型上的render方法
3. 将render返回的虚拟DOM转为真实DOM,随后呈现在页面中
*/
</script>
  1. 事件绑定

回顾一下原生事件绑定

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<button>btn1</button>
<button>btn2</button>
<button onclick="demo()">btn3</button>

<script type="text/javascript">
// 方法一
const btn1 = document.getElementById('btn1')
btn1.addEventListener('click',() => {
alert('按钮1被点击了')
})

// 方法二
const btn2 = document.getElementById('btn2')
btn2.onclick = (() => {
alert('按钮1被点击了')
})

// 方法三
function demo() {
alert('按钮3被点击了')
}
</script>
  1. react事件绑定
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
45
46
47
48
49
50
51
52
53
<div id="test"></div>
<script type="text/babel">
let that
// 1. 创建类式组件
class Weather extends React.Component {
constructor(props) {
super(props)
this.state={isHot:true} // 初始化状态
that = this // 声明一个that,让下面的也可以用这个this
// 解决this指向undefined问题
this.changeWeather = this.changeWeather.bind(this) // bind返回一个新函数,手动执行;新函数里的this变为Weather的实例对象,然后把函数放在实例自身,给函数起了个名字叫changeWeather,因此实例对象自身Weather,就多了一个方法叫changeWeather
}
render() {
// 读取状态
const {isHot} = this.state
// return <h1>今天天气{this.state.isHot ? '炎热' : '凉爽'}!</h1> 有const声明就不需要写的那么麻烦了
// return <h1 id="title">今天天气{isHot ? '炎热' : '凉爽'}!</h1>
// return <h1 onClick={demo()} >今天天气{isHot ? '炎热' : '凉爽'}!</h1> //直接在里面写onclick,demo不能用字符串形式,demo用函数的形式写出
return <h1 onClick={this.changeWeather} >今天天气{isHot ? '炎热' : '凉爽'}!</h1> // 但是加了括号表示立即执行,所以要去掉
// 所以这个的onClick={this.changeWeather}调用的是自身的changeWeather,不会去找原型上的changeWeather
}
changeWeather() {
// changeWeather放在哪儿?放在weather的原型对象上,供实例使用
// 通过weather实例调用changeWeather时,changeWeather中的this就是Weather的实例
// 所以不需要that
console.log(this.state.isHot)
// 发现this,undefined,为什么this会丢失? 举一个原生例子 看下一个js
// 所以这里相当于直接调用,changeWeather是作为onClick的回调,不是通过实例调用又因为类中方法默认开启局部的严格模式

}
}
// 2. 渲染组件到页面
ReactDOM.render(<render/>,document.getELementById('test'))
const title = document.getElementById('title')

/* 第一种 不建议要先document.getElementById('title')*/
title.addEventListener("click",() => {
console.log('标题被点击')
})
/*第二种 不建议要先document.getElementById('title')*/
title.onclick = () => {
console.log('标题被点击')
}
/*第三种 */
function changeWeather() { // 又需要将这个定义在类中
// 此处修改isHot的值
const {isHot} = this.state
console.log(isHot) // 这样会报错,因为state undefined,demo函数不在weather类中,根本没有关系的两个东西

// 所以
console.log(that.state.isHot)
}
</script>
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 Person {
constructor(name,age) {
this.name = name
this.age = age
}
study() {
// study方法放在原型对象上,供实例对象使用
// 通过Person实例调用study时,study中的this就是Person实例
console.log(this)
}
}
const p1 = new Person('tom',19)
p1.study() // 通过实例调用study对象
const x = p1.study
x() /* 这个时候study中的this打印为undefined,因为这个属于直接调用,上面p1.study() 是实例调用;
类中的定义的方法,全部局部自动开启严格模式
比如:
function demo() {
console.log(this) // window
}
demo()

function demo() {
'use strict'
console.log(this) // undefined
}
demo
*/
  1. setState 应用
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
 <div id="test"></div>
<script type="text/babel">
// 1. 创建类式组件
class Weather extends React.Component {

// 构造器调用几次————1 次
console.log(constructor)
constructor(props) {
super(props)
// 初始化状态
this.state={isHot:true,wind:'微风'}
// 解决this指向undefined问题
this.changeWeather = this.changeWeather.bind(this) // bind返回一个新函数,手动执行;新函数里的this变为Weather的实例对象,然后把函数放在实例自身,给函数起了个名字叫changeWeather,因此实例对象自身Weather,就多了一个方法叫changeWeather
}

// render 调用几次———— 1+n次 1是初始化那次,n是状态更新的次数
console.log(render)
render() {
// 读取状态
const {isHot} = this.state
return <h1 onClick={this.changeWeather} >今天天气{isHot ? '炎热' : '凉爽'}!,{wind}</h1> // 但是加了括号表示立即执行,所以要去掉
// 所以这个的onClick={this.changeWeather}调用的是自身的changeWeather,不会去找原型上的changeWeather
}

// changeWeather 调用几次———— 点几次调用几次
console.log(render)
changeWeather() {
// 获取原来的isHot值
const isHot = this.state.isHot
// 严重注意状态必须通过setState进行更新,且合并(更改isHot,后面的wind不会变)
this.setState({isHot:!isHot})
// 严重注意,状态不可直接更改,要借助内置API直接更改
// this.state.isHot = !isHot
console.log(this.state.isHot) // 这样两行改了,控制台点击true,false进行切换,但是react不认可,是单向数据流
}
}
// 2. 渲染组件到页面
ReactDOM.render(<Weather/>,document.getELementById('test'))
</script>
  1. 上述代码精简方式
    类中可以直接写赋值语句不需要let什么的声明,直接a=1
    所以
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
 <div id="test"></div>
<script type="text/babel">
// 1. 创建类式组件
class Weather extends React.Component {
// 可以不写构造器
// 想给Weather实例对象身上都添加一个state属性,那就拿出来直接赋值
state={isHot:true,wind:'微风'} // 初始化状态
// 下面这行代码,可以把changeWeather变成赋值语句
// this.changeWeather = this.changeWeather.bind(this)
// changeWeather = function(){
// // 这样changeWeather就放实例自身上了,但是这样仅仅只是换了个地方,之前在原型对象,现在在实例自身,所以换成箭头函数
// const isHot = this.state.isHot
// this.setState({isHot:!isHot})
// }

render() {
// 读取状态
const {isHot} = this.state
return <h1 onClick={this.changeWeather} >今天天气{isHot ? '炎热' : '凉爽'}!,{wind}</h1> // 但是加了括号表示立即执行,所以要去掉
// 所以这个的onClick={this.changeWeather}调用的是自身的changeWeather,不会去找原型上的changeWeather
}

// 自定义方法(赋值语句+箭头函数)
changeWeather = () => {
// 箭头函数没有自己的this,找外层函数的this
const isHot = this.state.isHot
this.setState({isHot:!isHot})
console.log(this) // Weather的实例对象
}
}
// 2. 渲染组件到页面
ReactDOM.render(<Weather/>,document.getELementById('test'))
</script>

【AIA】

  • state是组件对象最重要的属性,值是对象(可以包含多个key-value的组合)
  • 组件被称为“状态机”,像生活中的红绿灯,通过更新组件的state来更新对应的页面显示(重新渲染组件)
    [注意]
  1. 组件中render方法中的this为组件的实例对象
  2. 组件自定义方法中的this都是undefined,如何解决?
    a. 强制绑定this:通过函数对象的bind()
    b. 箭头函数
  3. 状态数据不能直接修改或更新

组件的第二个属性————props

类里面的this指向组件的实例对象,里面有一个props空对象

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
 <div id="test"></div>
<script type="text/babel">

// 1. 创建类式组件
class Person extends React.Component {
render() {
const {name,age,sex} = this.props
return (
// <ul>
// <li>姓名:{this.props.name}</li>
// <li>性别:{this.props.sex}</li>
// <li>年龄:{this.props.age}</li>
// </ul>
// 上面声明后可不需要写this.props
<ul>
<li>姓名:{name}</li>
<li>性别:{sex}</li>
<li>年龄:{age}</li>
</ul>
)
}
}
// 2. 渲染组件到页面
ReactDOM.render(<Person name="tom" sex="男" age="18" />,document.getELementById('test')) // 直接以key="valuw"形式放上去,可以接收多个
// 但是上述写法太麻烦了,所以
const p = {name="tom" sex="男" age="18"}
ReactDOM.render(<Person name={p.name} sex={p.sex} age={p.age} />,document.getELementById('test'))
// 这样还是太麻烦
ReactDOM.render(<Person {...p}/>,document.getELementById('test'))
console.log(...p) // 报错,仅仅适用于标签属性的传递,别的地方都不行
</script>

回顾一下扩展运算符

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
let arr1 = [1,2,3,4,5]
let arr2 = [2,4,6,8,10]
console.log(...arr1) // 1,2,3,4,5
console.log(arr1) // [1,2,3,4,5]
// 连接数组
let arr3 = [...arr1,...arr2]
console.log(arr3) // [1,2,3,4,5,2,4,6,8,10]

function sum(a+b) {
return a+b
}
console.log(sum(1,2))
// 若有很多数字或者数字不固定
function sum(...numbers) {
console.log(numbers) // [1,2] 是个数组,...可以批量都接收到
return numbers.reduce((preValue,currentValue) => {
// 函数体
return preValue + currentValue // 结果为3,1+2
})
}
console.log(sum(1,2))

// 原生中与react不同,react中babel和引入的react,可以直接用根本没有触发什么赋值对象
// 构造字面量对象时使用展开语法
let person = {name:'tom',age:'18'}
console.log(...person) // Found non-callable @@iterator at 展开运算符 意思是 对象类型没有接口 就是说展开运算符不能展开一个对象
// 但如果
let person = {name:'tom',age:'18'}
let person2 = {...person} // 表示赋值一个对象
person.name = 'jerry'
console.log(person2) // {name:'tom',age:'18'}
console.log(person) // {name:'jerry',age:'18'}

// 合并
let person3 = {...person,name:'jack',address:'地球'}
console.log(person3) // {name:'jerry',age:'18',address:'地球'}

props有一些限制

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
 <div id="test"></div>
<script type="text/babel">

// 1. 创建类式组件
class Person extends React.Component {
render() {
const {name,age,sex} = this.props
// this.props.name = 'jack' 此行代码会报错,因为props是只读的
return (
<ul>
<li>姓名:{name}</li>
<li>性别:{sex}</li>
<li>年龄:{age + 1}</li>
</ul>
)
}
}

// 对标签属性进行类型、必要性的限制
Person.propsTypes = { // props属性
// name:'必传,字符串'
// name:React.propTypes.string // React.propTypes 这是react内置对象,react15用,但react16被弃用了
// 所以引入依赖包prop-types .js ,引入后多了一个对象PropType
name:PropTypes.string.isRequired, // 加了isRequired表示名字必须传
sex:PropTypes.string, // 限制性别为字符串
age:PropTypes.number, // 限制年龄为数值
speak:PropTypes.func // 不能写function,会报错必须是一个function但得到了undefined,所以写func,function是个关键字
}
// 指定默认标签属性
Person.defaultProps = {
sex:'男', // sex默认值为男
age:18 // 年龄默认值为18
}
// 2. 渲染组件到页面
// 需求希望年龄+1, 又不改真实年龄结果变成年龄:191,需要改变基本数据类型,把字符串变成number
// ReactDOM.render(<Person name="tom" sex="男" age="18" />,document.getELementById('test')) // 直接以key="valuw"形式放上去,可以接收多个
// 变为
ReactDOM.render(<Person name="tom" sex="男" age={18} speak={speak}/>,document.getELementById('test'))

function speak() {
console.log('我在讲话')
}
</script>
  • 姓名必须指定,且为字符串类型
  • 性别为字符串类型,如果性别没有指定,默认为男
  • 年龄为字符串类型,且为数字类型,默认值为18

props的简写方式

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
<div id="test"></div>
<script type="text/babel">

// 1. 创建类式组件
class Person extends React.Component {
static propsTypes = {
name:PropTypes.string.isRequired,
sex:PropTypes.string,
age:PropTypes.number,
speak:PropTypes.func
}
// 指定默认标签属性
static defaultProps = {
sex:'男', // sex默认值为男
age:18 // 年龄默认值为18
}
render() {
const {name,age,sex} = this.props
// this.props.name = 'jack' 此行代码会报错,因为props是只读的
return (
<ul>
<li>姓名:{name}</li>
<li>性别:{sex}</li>
<li>年龄:{age + 1}</li>
</ul>
)
}
}

// 对标签属性进行类型、必要性的限制

// 2. 渲染组件到页面
// 需求希望年龄+1, 又不改真实年龄结果变成年龄:191,需要改变基本数据类型,把字符串变成number
// ReactDOM.render(<Person name="tom" sex="男" age="18" />,document.getELementById('test')) // 直接以key="valuw"形式放上去,可以接收多个
// 变为
ReactDOM.render(<Person name="tom" sex="男" age={18} speak={speak}/>,document.getELementById('test'))

function speak() {
console.log('我在讲话')
}
</script>
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
<div id="test"></div>
<script type="text/babel">
constructor(props) {
super(props) // 这个不用super接props也可以获取到,加了super与不加的区别在于this.props
console.log(this.props) // 打印实例自身的props,可以拿到;若没有super,undefined
// 总结:若不省略构造器,构造器是否接受props,是否传递给super,取决于:是否希望在构造器中通过this访问props
}
// 1. 创建类式组件
class Person extends React.Component {
static propsTypes = {
name:PropTypes.string.isRequired,
sex:PropTypes.string,
age:PropTypes.number,
speak:PropTypes.func
}
// 指定默认标签属性
static defaultProps = {
sex:'男', // sex默认值为男
age:18 // 年龄默认值为18
}
render() {
const {name,age,sex} = this.props
// this.props.name = 'jack' 此行代码会报错,因为props是只读的
return (
<ul>
<li>姓名:{name}</li>
<li>性别:{sex}</li>
<li>年龄:{age + 1}</li>
</ul>
)
}
}

ReactDOM.render(<Person name="tom"/>,document.getELementById('test'))
</script>

函数组件使用props

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
<div id="test"></div>
<script type="text/babel">
function Person() {
const {name,age,sex} = props
return (
<ul>
<li>姓名:{name}</li>
<li>性别:{sex}</li>
<li>年龄:{age}</li>
</ul>
)
}
// 限制写在外面,它不像类式组件有static属性
// 对标签属性进行类型、必要性的限制
Person.propsTypes = { // props属性
name:PropTypes.string.isRequired, // 加了isRequired表示名字必须传
sex:PropTypes.string, // 限制性别为字符串
age:PropTypes.number // 限制年龄为数值
}
// 指定默认标签属性
Person.defaultProps = {
sex:'男', // sex默认值为男
age:18 // 年龄默认值为18
}
// 渲染到组件页面
ReactDOM.render(<Person name="jerry" sex="男" age={18}/>,document.getElementById('test'))
</script>

组件的第三个属性————ref

  • ref字符串形式
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
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
<div id="test"></div>
<script type="text/babel">
// 1. 创建类式组件
class Demo extends React.Component {
// 展示左侧输入框数据——字符串方法
showData = () => {
// 不需要写的这么麻烦
// const input = document.getElementById('input1')
// alert(input.value)

console.log(this.refs.input1) // 这个是真实DOM了,所以可以
const {input1} = this.refs
alert(input1.value)
}
showData2 = () => {
const {input2} = this.refs
alert(input2.value)
}

// ref回调方法
// 展示左侧输入框数据
showData = () => {
// 不需要写的这么麻烦
// const input = document.getElementById('input1')
// alert(input.value)

console.log(this.refs.input1) // 这个是真实DOM了,所以可以
const {input1} = this
alert(input1.value)
}
showData2 = () => {
const {input2} = this
alert(input2.value)
}
render() {
return (
// 字符串类型的ref不太推荐,存在效率问题,写多了效率不高
// <div>
// {/* <input type="text" id="input1" placeholder="点击按钮提示数据"/> 可以不用写id,换成ref */}
// <input type="text" ref="input1" placeholder="点击按钮提示数据"/>
// <button onClick={this.showData}>点我提示左侧数据</button>
// <input type="text" onBlur={this.showData2} ref="input2" placeholder="诗句焦点提示数据"/>
// {/* 只要写ref,就会放到Demo实例对象refs中 */}
// </div>

<div>
{/* <input type="text" ref={(a)=> {console.log(a)} }placeholder="点击按钮提示数据"/> */}
{/* 这个a,打印出来后是 <input type="text" placeholder="点击按钮提示数据"/>
ref属性当前所在的节点 */}

{/* <input type="text" ref={(a)=> {this.input1 = a} }placeholder="点击按钮提示数据"/> */}
{/* 把a这个节点放在组件实例自身上 */}

<input type="text" ref={currentNode => this.input1 = currentNode }placeholder="点击按钮提示数据"/>
<button onClick={this.showData}>点我提示左侧数据</button>
<input type="text" onBlur={this.showData2} ref={c => this.input2 = c} placeholder="失去焦点提示数据"/>
</div>

)

}
}

ReactDOM.render(<Demo/>,document.getELementById('test'))
</script>
  • 回调ref中执行次数问题,回调函数形式
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
<div id="test"></div>
<script type="text/babel">
// 1. 创建类式组件
class Demo extends React.Component {
showInfo = () => {
const {input1} = this
alert(input1.value)
}
changeWeather = () => {
// 获取原来的状态
state = {isHot:true}
// 更新状态
this.setState({isHot:!isHot})
}
saveInput = (c) => {
this.input1 = c
console.log('@',c)
}
render() {
const {isHot} = this.state
return (
<div>
<h2>今天天气很{isHot ? '炎热' : '凉爽'}</h2>
{/* <input ref={c => this.input1 = c;console.log('@',c)}/> */}
{/* 解决第一次传入参数null,第二次会传入参数DOM元素 通过ref回调函数定义成class的绑定函数方式*/}
<input ref={this.saveInput} type="text"/>
<button onClick={this.showInfo}>点我提示输入的数据</button>
<button onClick={this.changeWeather}>点我切换天气</button>
</div>
)
}
}
/*
如果ref回调函数是以内联函数的方式定义的,在更新的过程中会被执行两次,第一次传入参数null,第二次会传入参数DOM元素
这是因为在每次渲染时会创建一个新的函数实例,所以react清空旧的ref并且设置新的
通过ref回调函数定义成class的绑定函数的方式可以避免上述问题
*/
ReactDOM.render(<Demo/>,document.getELementById('test'))
</script>
  • createRef形式
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
<div id="test"></div>
<script type="text/babel">
// 1. 创建类式组件
class Demo extends React.Component {
/*
React.createRef调用后可以返回一个容器,该容器可以存储被ref所标识的节点,但是该容器是专人专用
*/
myRef = React.createRef()
myRef2 = React.createRef()
// 展示左侧输入框数据
showData = () => {
console.log(this.myRef) // {currrent:input}
console.log(this.myRef.current) // 拿的是这个input节点 <input type="text" placeholder="点击按钮提示数据"/>
console.log(this.myRef.current.value) // input框输入什么,这里显示什么
}
// 展示右侧输入框数据
showData2 = () => {
console.log(this.myRef2.current.value) // input框输入什么,这里显示什么
}
render() {
return (
<div>
<input ref={this.myRef} type="text" placeholder="点击按钮提示数据"/>
{/* input被存在this.myRef中 */}
<button onClick={this.showData}>点我提示左侧的数据</button>
<input ref={this.myRef2} type="text" onBlur={this.showData2} placeholder="点击按钮提示数据"/>
</div>
)
}
}
ReactDOM.render(<Demo/>,document.getELementById('test'))
</script>

react中的事件处理

  1. 通过onXXX属性指定事件处理函数(注意大小写)
  • react使用的是自定义(合成)事件,而不是使用的原生DOM事件————为了更好的兼容
  • react中事件是通过事件委托方式处理的(委托给组件最外层元素)————为了高效
  1. 通过event.target得到发生事件的DOM元素(不要过度使用Refs,可以用这个代替)
1
2
3
4
5
6
7
showData = () => {
console.log(this.myRef2.current.value)
}
showData = (event) => {
console.log(event.target.value)
}
// 但像myref,在上述代码input中无法省略,因为这个

受控组件,非受控组件

  • 非受控组件
    通过手动操作dom方式获取文本框的值,文本框状态不受react组件的state中状态控制,直接通过原生dom获取输入框的值

实现步骤:

  1. 导入createRef函数
  2. 调用createRef函数,创建一个ref对象,存储到名为msgRef的实例属性中
  3. 为input添加ref属性,只为msgRef
  4. 在按钮的事件处理程序中,通过msgRef.current即可拿到input对应的dom元素,而其中msg.current.value拿到的就是文本框的值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import React,{createRef} from "react"
class Input extends React.Component {
// 这个实例属性可以自定义
msgRef = createyRef()
getValue=() => {
// 通过msgRef获取input value值
console.log(this.msgRef.current.value)
}
render() {
return (
<>
<input type="text" ref={this.msgRef}/>
<button onClick={this.getValue}>点击获取输入框的值</button>
</>
)
}
}

这并不是react特有的行为,这其实与javascript函数工作原理有关。通常情况下,如果你没有在方法后面添加(),例如onClick={this.handleClick},你应为这个方法绑定this

  • 受控组件
    input框自己的状态被react组件控制
    受控组件就是说可以被react动态控制的组件

实现步骤:

  1. 在组件的state中声明一个组件的状态数据
  2. 将状态数据设置为input标签元素的value属性的值
  3. 为input添加onchange事件
  4. 在事件处理程序中,通过事件对象e获取到当前文本框的值(即用户当前输入的值)
  5. 调用setState方法,将文本框的值作为state状态的最新值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Counter extends React.Component {
// 1. 声明一个用来控制input value属性的react组件自己的状态
state = {
message:'this is a message'
}
// 回调函数
inputChange = (e) => {
console.log('change事件触发了',e)
// 4. 需要在这里拿到输入框最新的值 交给state中的message
this.setState({
message:e.target.value
})
}
render() {
return (
// 2. 给input框的value属性绑定 react state
// 3. 给input框绑定一个change事件 为了拿到当前输入框中的数据 (onchange指定事件回调)
<input type="text" value={this.state.message} onChange={this.inputChange}/>
)
}
}

优化上述代码

若有很多个input,比如用户名,密码等等,总不能绑定一个又一个onchange事件

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
class Counter extends React.Component {
// 1. 声明一个用来控制input value属性的react组件自己的状态
state = {
username:'',
password:''
}

// 回调函数
saveFormData = (dataType) => {
console.log(dataType)
return (e) => { // 把saveFormData返回值返回的函数交给onchange作为回调
console.log(e.target.value)
this.setState({
// dataType:e.target.value // 这样写是往state中放了一个新的Key,在用户名输入的内容,会出现在dataType这个key后面;压根就没有读取

// 需要读取变量,变量用[]
[dataType]:e.target.value
})
}
}

// 表单提交的回调
handleSubmit = (e) => {
e.preventDefault() // 阻止表单默认提交
const {username,password} = this.state
alert(`用户名是${username}`,密码是${password})
}
render() {
return (
<form onSubmit={this.handleSubmit}>
用户名<input type="text" onChange={this.saveFormData('username')}/>
{/* 但若是这么写就是把saveFormData返回值作为回调,立即执行 saveFormData返回值是undefined */}
密码<input type="text" onChange={this.saveFormData('password')}/>
</form>
)
}
}

高阶函数和函数柯里化

  1. 上述saveFormData这就是高阶函数
    高阶函数:
    如果一个函数符合下面两个规范中的任何一个,那该函数就是高阶函数
    1. 若A函数接收的参数是一个函数,那么A就可以称之为高阶函数
    2. 若A函数调用的返回值依然是一个函数,那么A就可以称之为高阶函数
    常见的高阶函数:
    promise、setTimeout、arr.map()等等
  2. 函数柯里化
    通过函数调用继续返回函数的方式,实现多次接收参数最后统一处理的函数编码形式
1
2
3
4
5
function sum(a,b,c) {
return a+b+c
}
const result = sum(1,2,3)
console.log(sum )

不用柯里化实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//  saveFormData = (dataType,value) => {     
// this.setState({[dataType]:value})
// }
// 精简代码
saveFormData = (dataType,e) => {
this.setState({[dataType]:e.target.value})
}

render() {
return (
// <form onSubmit={this.handleSubmit}>
// 用户名<input type="text" onChange={(e) => {this.saveFormData('username',e.target.value)}}/>
// {/* 但若是这么写就是把saveFormData返回值作为回调,立即执行 saveFormData返回值是undefined */}
// 密码<input type="text" onChange={(e) => {this.saveFormData('password',e.target.value)}}/>
// </form>

<form onSubmit={this.handleSubmit}>
用户名<input type="text" onChange={(e) => {this.saveFormData('username',e)}}/>
{/* 但若是这么写就是把saveFormData返回值作为回调,立即执行 saveFormData返回值是undefined */}
密码<input type="text" onChange={(e) => {this.saveFormData('password',e)}}/>
</form>
)
}

react生命周期

生命周期回调函数 又叫 生命周期钩子函数 生命周期钩子 生命周期函数

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
45
46
47
48
49
50
51
52
53
54
55
56
 <div id="test"></div>
<script type="text/babel">
class Life extends React.Component {
// 要求2秒完全可见到消失
state = {opacity:1}
death = () => {
// 卸载组件前要先清除定时器,拿到定时器的id
clearInterval(this.timer)
// 卸载组件回调
// ReactDOM.unmountComponent() // 若有很多个容器不能用这个
ReactDOM.unmountComponentAtNode(document.getElementById('test'))
}

// 组件挂载完毕 后调用
componentDidMount() { // 这个不需要像death一样,写一个箭头函数,因为它与render是兄弟,后期是通过Life的实例对象 .调用的
this.timer = setInterval(() => {
// 1. 获取原状态,这里不能用const,const声明变量不可修改
let {opacity} = this.state
// 减小0.1
opacity -= 0.1
if(opacity <=0) opacity = 1 // 为什么是<=0因为在js中0.1+0.2不一定等于0.3
// 设置新的透明度
this.setState ({opacity})
},200)
}

// 组件将要卸载
// componentWillUnmount() {
// 这样清楚定时器也可以
// clearInterval(this.timer)
// }

// render调用的时机:初始化渲染,状态更新之后
render() {
// 这样写代码直接卡死了,cpu一路飙升;因为设置更新状态,一更新状态就调render, 引发无限递归,多次调用render
// setInterval(() => {
// // 1. 获取原状态,这里不能用const,const声明变量不可修改
// let {opacity} = this.state
// // 减小0.1
// opacity -= 0.1
// if(opacity <=0) opacity = 1 // 为什么是<=0因为在js中0.1+0.2不一定等于0.3
// // 设置新的透明度
// this.setState ({opacity})
// },200)
return (
<div>
{/* h2中写style={{opacity:this.state.opacity}} */}
<h2>只要学不死,就往死里学</h2>
<button onClick={this.death}>不活了</button>
</div>
)
}
}

ReactDOM.render(<Life/>,document.getELementById('test'))
</script>
  • 组件是否应该被更新shouldComponentUpdate
    true可以更新
    false不可以更新
    不写这个钩子,默认true
  • forceUpdate强制更新
    可以绕过shouldComponentUpdate,直接进行更新

父组件render

componentWillReceiveProps这个第一次传的不算,以后传的才算

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
class A extends React.Component{
state = {carName:'宝马'}
changeCar = () => {
this.setState({carName:'奔驰'})
}
render() {
return (
<div>
<div>我是A组件</div>
<button onClick={this.changeCar}></button>
<B carName={this.state.carName}/>
</div>
)
}
}
class B extends React.Component{
componentWillReceiveProps(props) {
console.log('B---componentWillReceiveProps',props)
}
render() {
return (
<div>
<div>我是B组件,接收的车是:{this.props.carName}</div>
</div>
)
}
}

[AIA]

  1. 初始化阶段: 由ReactDoM.render()触发—初次渲染
    constructor()
    componentWillMount()
    render()
  2. componentDidMount()更新阶段: 由组件内部this.setSate()或父组件重新render触发
    shouldComponentUpdate()componentwillUpdate()
    render()
    componentDidUpdate()
  3. 卸载组件:由ReactDOM.unmountComponentAtNode()触发componentwillUnmount()
    新生命周期
    去掉三个钩子,加了两个钩子
    componentWillMount() componentwillUpdate() componentwillUnmount()
  • getDerivedStateFromProps:若state的值任何时候都取决于props,可以使用
  • getSnapShotBeforeUpdate(preProps,preState,snapValue) 在更新前获取快照
    在页面调完时拿到数据在处理时使用,此生命周期的任何返回值将作为参数传递给componentDidUpdate

觉得写的不错 就可怜可怜博主吧.

其他文章
cover
Java学习
  • 21/10/19
  • 10:46
  • 2.8k
  • 10
cover
Nuxtjs学习
  • 21/10/19
  • 10:20
  • 1.2k
  • 4