组件与模块
组件与模块
以下为学习过程中的极简提炼笔记,以供重温巩固学习
学习准备
准备工作
html+css+JavaScript vue
其中需要掌握JavaScript中的
- 判断this的指向
- class(类)、实例、继承
- ES6语法规范(箭头函数、模板字符串、解构赋值)
- npm等包管理器(学习react脚手架时需要)
- 原型&原型链
- 数组的常用方法(统计、遍历、过滤、条件筛选、求和、最值)
- 模块化
学习目的
模块与组件
模块的定义
- 向外界提供特定功能的js程序。
- 随着业务逻辑增加,代码越来越多且复杂,
- 此时js一般会拆成多个js文件来编写,一般一个js文件就是一个模块
- 一般一个模块,一个js文件,对应不同的业务,对应一个独立的功能
- 作用:复用js,简化js的编写,提高js的运行效率
- 模块化:当应用的js都以模块来编写的, 这个应用就是一个模块化的应用
组件的定义
- 用来实现局部功能效果的代码和资源的集合(html/css/js/image等等)。
- 比如一个功能齐全的Header栏就是一个组件。
- 复用编码, 简化项目编码, 提高运行效率
- 组件化:当应用是以多组件的方式实现, 这个应用就是一个组件化的应用
面向组件编程
- 准备:
- 浏览器安装React Developer Tools
- 安装React Developer Tools调试工具后,在浏览器打开使用React编写的网站,审查元素时调试栏出现component和profiler
- components:查看React网页的组件结构,确认由多少个组成、组件中有哪些属性、组件如何嵌套组成等
- profiler:记录网站性能,如网站的渲染时间、组件加载速度情况等
定义/创建组件
当应用是以多组件的方式实现,这个应用就是一个组件化的应用
注意1:组件也是需要符合JSX模板标签的规定
- 组件名必须是首字母大写
- 虚拟DOM元素/模板标签只能有一个根元素
- 虚拟DOM元素/模板标签必须有结束标签
< />
,标签必须闭合
React提供了两个定义/创建组件的方式:函数式组件和类式组件
React中的组件,也是写在JSX模板标签中
React组件渲染时,通过ReactDOM.render(<MyComponent/>,document.getElementById('test'))
,将其渲染给HTML中的准备好的容器
函数式组件
函数式组件:通过函数定义的react组件,适用于【简单组件】的定义
- 相对简单,容易上手
//1.先创建函数,函数可以有参数,也可以没有,但是必须要有返回值 返回一个虚拟DOM
function Welcome(props) {
console.log(this); //此处的this是undefined,因为babel编译后开启了严格模式
return <h1>Hello, {props.name}</h1>;
}
//2.渲染组件到页面
ReactDOM.render(<Welcome name = "milo" />,document.getElementById("div"));
上面的代码经历了以下几步
- 我们调用
ReactDOM.render()
函数,并传入<Welcome name="milo" />
作为参数。 - React调用
Welcome
组件,并将{name: 'milo'}
作为 props 传入。 Welcome
组件将Hello, milo
元素作为返回值。- ReactDOM 将 DOM高效地更新为
Hello,milo
。
函数式组件的渲染
执行了ReactDOM.render(<MyComponent/>.......
之后,发生了什么?
- React解析组件标签
<(大写开头)组件名/>
,找到了MyComponent组件。 - 发现组件是使用函数定义的,随后调用该函数,将返回的虚拟DOM转为真实DOM,随后呈现在页面中。
- 组件渲染后,在浏览器调试中,只能看到翻译为JS后的、真正执行的HTML内容
- 如需在组件渲染后再查看虚拟DOM结构/模板标签组件结构,需要到调试中的React Developer Tools中的Component
- 在React Developer Tools的Component中,可以看到组件的props属性,以及react版本
- 函数式组件的渲染,是模板标签中的函数,被React(准确说是ReactDOM)调用而渲染的
- 我们只需要在函数中写好JSX组件模板标签,在ReactDOM.render方法调用中写好被调用渲染的组件标签(
<(大写开头)组件名/>
) - 函数需要有返回值,返回的就是需要渲染的模板标签
- 函数式组件中的this是undefined,因为babel编译后开启了严格模式(禁止自定义函数中的this指向window)
- 严格模式是采用具有限制性 JavaScript 变体的一种方式,从而使代码隐式地脱离“马虎模式/稀松模式/懒散模式“(sloppy)模式
- 我们只需要在函数中写好JSX组件模板标签,在ReactDOM.render方法调用中写好被调用渲染的组件标签(
注意2:渲染组件标签的基本流程
- React 内部会创建组件实例对象
- 调用
render()
得到虚拟 DOM /组件模板标签,并解析为真实 DOM /HTML标签- 解析后的真实HTML标签,插入到指定的HTML页面元素内部
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>1_函数式组件</title>
</head>
<body>
<!-- 准备好一个“容器” -->
<div id="test"></div>
<!-- 引入react核心库 -->
<script type="text/javascript" src="../js/react.development.js"></script>
<!-- 引入react-dom,用于支持react操作DOM -->
<script type="text/javascript" src="../js/react-dom.development.js"></script>
<!-- 引入babel,用于将jsx转为js -->
<script type="text/javascript" src="../js/babel.min.js"></script>
<script type="text/babel">
//1.创建函数式组件
function MyComponent(){
console.log(this); //此处的this是undefined,因为babel编译后开启了严格模式
return <h2>我是用函数定义的组件(适用于【简单组件】的定义)</h2>
}
//2.渲染组件到页面
ReactDOM.render(<MyComponent/>,document.getElementById('test'))
/*
执行了`ReactDOM.render(<MyComponent/>.......`之后,发生了什么?
1. React解析组件标签`<(大写开头)组件名/>`,找到了MyComponent组件。
2. 发现组件是使用函数定义的,随后调用该函数,将返回的虚拟DOM转为真实DOM,随后呈现在页面中。
3. 组件渲染后,在浏览器调试中,只能看到翻译为JS后的、真正执行的HTML内容
4. 如需在组件渲染后再查看虚拟DOM结构/模板标签组件结构,需要到调试中的React Developer Tools中的Component
5. 在React Developer Tools的Component中,可以看到组件的props属性,以及react版本
6. 函数式组件的渲染,是模板标签中的函数,被React(准确说是ReactDOM)调用而渲染的
- 我们只需要在函数中写好JSX组件模板标签,在ReactDOM.render方法调用中写好被调用渲染的组件标签(`<(大写开头)组件名/>`)
- 函数式组件中的this是undefined,因为babel编译后开启了严格模式(禁止自定义函数中的this指向window)
*/
</script>
</body>
</html>
复习:什么是类?
复习类的相关知识
- 类中的构造器不是必须要写的,要对实例进行一些初始化的操作,如添加指定属性时才写。
- 如果
A类
继承了B类
,且A类
中写了构造器,那么A类
构造器中的super
是必须要调用的。 - 类中所定义的方法,都放在了类的原型对象上,供实例去使用。
- 定义类的关键字是class
- 类的使用:用类创建实例对象,
- 即:
class 类名 {实例对象参数}
- 打印类名,可得:
类名对应的 {实例对象参数}
- 即:
- 类里面分为属性和方法
- 借助 constructor 构造器方法/构造器函数接收
- 接收语法:
constructor(类名对应实例对象的形参){将传入的参数放入实例自身,通过this.属性,来赋值给自身属性}
- 一般方法:除了constructor 构造器方法以外的方法,都是一般方法;
- 所有根据业务场景写的方法都是一般方法
- 一般方法放在类的原型对象上,供实例使用
- 也就是原型链的查找规则,当读取属性自身不存在的属性,或调用了自身不存在的方法,沿着原型链查找(调试打印中的
__proto__
下)
- 也就是原型链的查找规则,当读取属性自身不存在的属性,或调用了自身不存在的方法,沿着原型链查找(调试打印中的
- 通过
class B extends A
,B作为子类,可以从父类A中,直接继承构造器,让子类也能使用父类原型上的方法- 可以继续在子类中,写入子类独有的构造器
- 类语法规定,在子类中,必须调用子类的super构造器,来确认接收继承自父类的构造器/在子类中通过super调用父类构造器
- 通过
super(共有的继承属性1,共有的继承属性2,共有的继承属性3...)
完成传递,且需要放在其他属性之前 - 在子类中,不写方法的情况下,在原型链上是看不到子类的方法的,能看到继承自父类构造器的方法
constructor class B子类
- 在子类中直接调用
子类中没有/父类中的方法
时,会自动通过原型链,找其缔造者的原型对象,原型对象没有则再根据原型链找到父类__proto__下的对应的方法 - 方法放在原型链上/父类上,大家(父类子类)都能使用,增加了复用性
- 如需在子类中调用方法时再嵌入子类独有的方法,需要在子类中重写从父类继承过来的方法;
- 重写方法后,子类的原型链上就有子类的方法了
- 按照原型链查找规则,在子类中已经查找到方法的情况下,不会再进一步往父类查找/使用父级的原型方法
- 通过子类实例调用子类中的方法时,子类方法中的this就是指向子类实例(因此study中的this就是子类实例)
- 子类实例中,可以添加子类独有的属性,不需要再传递或者接
- 举例:自身没有speak方法,通过原型链,找其缔造者的原型对象,原型对象没有则根据原型链找到父类
__proto__
下的speak方法

创建一个Person类
// 创建一个Person类
class Person {
//构造器方法
constructor(name,age){
//构造器中的this是谁?—— 类的实例对象
this.name = name
this.age = age
}
//一般方法
speak(){
//speak方法放在了哪里?——类的原型对象上,供实例使用
//通过Person实例调用speak时,speak中的this就是Person实例
console.log(`我叫${this.name},我年龄是${this.age}`);
}
}
// 创建一个Person类的实例对象
const p1 = new Person('milo',18);
// 打印输出Person类的实例对象(p1是Person类的实例对象)
console.log(p1);
// 输出的结果为:Person {},即:由Person创建new出来的实例对象{}
Person {name: 'milo', age: 18}
//通过Person实例.点调用speak时,speak中的this就是Person实例
p1.speak(); //我叫milo,我年龄是18
举例:
- 如需在子类中调用方法时再嵌入子类独有的方法,需要在子类中重写从父类继承过来的方法
- 重写方法后,子类的原型链上就有子类的方法了
- 按照原型链查找规则,在子类中已经查找到方法的情况下,不会再进一步往父类查找/使用父级的原型方法
- 通过子类实例调用子类中的方法时,子类方法中的this就是指向子类实例
- 子类实例中,可以添加子类独有的属性,不需要再传递或者接

创建一个Student类,继承于Person类
//创建一个Student类,继承于Person类
class Student extends Person {
// 写入子类独有的构造器
constructor(name,age,grade){
super(name,age)
this.grade = grade
// 给所有Student缔造的子类实例,都添加一个属性,名为school,值为'尚硅谷'
this.school = '尚硅谷'
// 子类实例中,可以添加子类独有的属性,不需要再传递或者接
}
// 在子类中,不写方法的情况下,在原型链上是看不到子类的方法的,能看到继承自父类构造器的方法
// 如需在子类中调用方法时再嵌入子类独有的方法,需要在子类中重写从父类继承过来的方法
speak(){
console.log(`我叫${this.name},我年龄是${this.age},我读的是${this.grade}年级`);
this.study()
}
study(){
//study方法放在了哪里?——类的原型对象上,供实例使用(放在了子类上)
//通过Student实例调用study时,study中的this就是Student实例(因此study中的this就是子类实例)
console.log('我很努力的学习');
// 通过子类实例调用子类中的方法时,子类方法中的this就是指向子类实例
}
}
// 创建一个Student类的实例对象
const p2 = new Student('milo2',22,'大四');
console.log(p2); //Student {name: 'milo2', age: 22, grade: '大四', school: '工程大'}
// 自身没有speak方法,通过原型链,找其缔造者的原型对象
// 原型对象没有则根据原型链找到父类__proto__下的speak方法
p2.speak(); //我叫milo2,我年龄是22,我读的是大四年级
创建实例对象后,通过调用方法,会自动寻找原型链上继承的方法
// 创建一个Person类的实例对象
const p1 = new Person('milo',18);
// 打印输出Person类的实例对象(p1是Person类的实例对象)
console.log(p1);
// 输出的结果为:Person {},即:由Person创建new出来的实例对象{}
Person {name: 'milo', age: 18}
//通过Person实例.点调用speak时,speak中的this就是Person实例
p1.speak(); //我叫milo,我年龄是18
==================================
// 创建一个Student类的实例对象
const p2 = new Student('milo2',22,'大四');
console.log(p2); //Student {name: 'milo2', age: 22, grade: '大四', school: '工程大'}
// 自身没有speak方法,通过原型链,找其缔造者的原型对象
// 原型对象没有则根据原型链找到父类__proto__下的speak方法
p2.speak(); //我叫milo2,我年龄是22,我读的是大四年级
- 按照原型链查找规则,在子类中已经查找到方法的情况下,不会再进一步往父类查找/使用父级的原型方法

总结
- 类中的构造器不是必须要写的,要对实例进行一些初始化的操作,如添加指定属性时才写。
- 如果
A类
继承了B类
,且A类
中写了构造器,那么A类
构造器中的super
是必须要调用的。 - 类中所定义的方法,都放在了类的原型对象上,供实例去使用。
复习结束,开始创建类式组件
类式组件
类式组件:通过类定义的react组件,适用于【复杂组件】的定义
函数式组件--通过函数定义--函数在react中作为组件使用--函数名就是组件名
- 通过React Developer Tools开发者调试工具中的components,能看到组件名
类式组件--通过继承自React中
内置的React.Component类
来定义--必须写render(){return返回值}渲染组件
方法--类名就是组件名- 如果在React中使用类式来定义组件,必须要继承自React中内置的React.Component类
- 如不继承,则为普通的类,而不构成组件;普通的类并非组件,无法作为组件使用
- 通过继承自React中内置的React.Component类来定义组件时,可以省略构造器,也就连带省略了子类中的super调用;即子类中直接继承了父类属性,省略了super接收过程
- 如果没有额外的初始化需要,则可以省略构造器,如果子类中需要传入独有的数据,则需要加入构造器&super接收父类
- 在继承自React.Component的子类中,必须存在
render(){return返回值}渲染组件
方法- render()方法中必须要有返回值,返回的是JSX模板标签结构(需要显示什么就返回对应的JSX模板)
- 如果在React中使用类式来定义组件,必须要继承自React中内置的React.Component类
// 1.创建类式组件
class MyComponent extends React.Component{
render(){
//render是放在哪里的?—— MyComponent的原型对象上,供实例使用。
//render中的this是谁?—— MyComponent的实例对象 <=> MyComponent组件实例对象。
console.log('render中的this:',this);
return <h2>我是用类定义的组件(适用于【复杂组件】的定义)</h2>
}
}
// 2.渲染组件到页面
ReactDOM.render(<MyComponent/>,document.getElementById('test'))
上面的代码经历了以下几步
- 我们调用
ReactDOM.render()
函数,并传入<MyComponent/>
作为参数。 - React 调用
MyComponent
组件。 MyComponent
组件将我是用类定义的组件(适用于【复杂组件】的定义)
元素作为返回值。- ReactDOM 将 DOM 高效地更新为
我是用类定义的组件(适用于【复杂组件】的定义)
。
类式组件的渲染
执行了ReactDOM.render(<MyComponent/>.......
之后,发生了什么?
- React解析组件标签
<(大写开头)组件名/>
,找到了MyComponent组件。 - 发现组件是使用类定义的,随后new出来该类的实例,并通过该实例调用到原型链上的render方法。
- 将render返回的虚拟DOM模板标签,转为真实DOM,随后呈现在页面中
- 组件渲染后,在浏览器调试中,只能看到翻译为JS后的、真正执行的HTML内容
- 如需在组件渲染后再查看虚拟DOM结构/模板标签组件结构,需要到调试中的React Developer Tools中的Component
- 在React Developer Tools的Component中,可以看到组件的props属性,以及react版本
- 函数式组件的渲染,是模板标签中的函数,被React(准确说是ReactDOM)调用而渲染的
- 我们只需要在函数中写好JSX组件模板标签,在ReactDOM.render方法调用中写好被调用渲染的组件标签(
<(大写开头)组件名/>
) - 函数需要有返回值,返回的就是需要渲染的模板标签
- 函数式组件中的this是undefined,因为babel编译后开启了严格模式(禁止自定义函数中的this指向window)
- 严格模式是采用具有限制性 JavaScript 变体的一种方式,从而使代码隐式地脱离“马虎模式/稀松模式/懒散模式“(sloppy)模式
- 我们只需要在函数中写好JSX组件模板标签,在ReactDOM.render方法调用中写好被调用渲染的组件标签(
- 发现组件是使用类定义的,随后new出来该类的实例,并通过该实例调用到原型链上的render方法

其他知识
包含表单元素的组件分为非受控租价与受控组件
- 受控组件:表单组件的输入组件随着输入并将内容存储到状态中(随时更新)
- 非受控组件:表单组件的输入组件的内容在有需求的时候才存储到状态中(即用即取)
组件实例三大属性
1. state
React 把组件看成是一个状态机(State Machines)。通过与用户的交互,实现不同状态,然后渲染 UI,让用户界面和数据保持一致。
React 里,只需更新组件的 state,然后根据新的 state 重新渲染用户界面(不要操作 DOM)。
简单的说就是组件的状态,也就是该组件所存储的数据
1.1 state在类式组件Weather中的使用——标准写法
需求: 定义一个展示天气信息的组件
默认展示天气炎热或凉爽
点击文字切换天气
- 首先创建
Weather
类式组件
//1.创建组件
class Weather extends React.Component{
//初始化状态
}
- 在
Weather
类中使用构造器初始化状态
//构造器调用几次? ———— 1次
constructor(props){
super(props)
//初始化状态
this.state = {isHot:false}//天气热么:默认不热
}
这里state
是组件对象最重要的属性,值是对象(可以包含多个key-value
的组合),形如{isHot:false,wind:'微风'}
3. render
读取state
状态,return
中返回要输出的内容
//render调用几次? ———— 1+n次 1是初始化的那次 n是状态更新的次数
render(){
//读取状态
const {isHot,wind} = this.state
return <h1 onClick={this.changeWeather}>今天天气很{isHot ? '炎热' : '凉爽'}</h1>
}
- 这里
return
中,在<h1>
标签内写onclick
事件时,要写成onClick
! - 组件中
render
方法中的this
为组件实例对象
- 自定义方法
changeWeather
,用来更新组件的state
,以此来更新对应的页面显示(重新渲染组件)
//changeWeather调用几次? ———— 点几次调几次
changeWeather(){
//获取原来的isHot值
const isHot = this.state.isHot
//严重注意:状态必须通过setState进行更新,且更新是一种合并,不是替换。
this.setState({isHot:!isHot})
}
changeWeather
放在哪里? ————Weather
的原型对象上,供实例使用
注意⚠️!!!
- 由于
changeWeathe
r是作为onClick
的回调,所以不是通过实例调用的,是直接调用 - 并且类中的方法默认开启了局部的严格模式,所以
changeWeather
中的this
为undefined
- 那找不着初始化状态中的
isHot
怎么办?为了解决这个问题,有两种方法:
法一:bind()方法
通过函数对象的bind()
方法,强制绑定this
,需要在构造器中加入下面一行代码
//解决changeWeather中this指向问题
this.changeWeather = this.changeWeather.bind(this)
法二:赋值语句的形式+箭头函数(常用!)
- 使用赋值语句的形式创建自定义方法,这样
changeWeather
从Weather
类的原型对象方法变成了实例自身的方法 - 箭头函数一大特点是:没有自己的
this
,会找其外层函数的this
作为其this
使用,其外层就是组件实例自身的this
//自定义方法————要用赋值语句的形式+箭头函数
changeWeather = ()=>{
//获取原来的isHot值
const isHot = this.state.isHot
//状态必须通过setState进行更新,且更新是一种合并,不是替换。
this.setState({isHot:!isHot})
}
- 渲染组件到页面
//2.渲染组件到页面
ReactDOM.render(<Weather/>,document.getElementById('test'))
总结(标准写法完整代码)
//1.创建组件
class Weather extends React.Component{
constructor(props){
super(props)
//初始化状态
this.state = {isHot:false}
//解决changeWeather中this指向问题
this.changeWeather = this.changeWeather.bind(this)
}
render(){
//读取状态
const {isHot,wind} = this.state
return <h1 onClick={this.changeWeather}>今天天气很{isHot ? '炎热' : '凉爽'}</h1>
}
changeWeather(){
//获取原来的isHot值
const isHot = this.state.isHot
//状态通过setState进行更新
this.setState({isHot:!isHot})
}
}
//2.渲染组件到页面
ReactDOM.render(<Weather/>,document.getElementById('test'))
1.2 简写方式(开发常用)
- 自定义方法用赋值语句的形式+箭头函数创建,就不需要在构造器中强制绑定
this
了 - 直接初始化
state
状态,就不用在构造器中初始化了 - 因为以上两点,所以不需要构造器
constructor(){}
了
//1.创建组件
class Weather extends React.Component{
//初始化状态
state = {isHot:false}
render(){
//读取状态
const {isHot,wind} = this.state
return <h1 onClick={this.changeWeather}>今天天气很{isHot ? '炎热' : '凉爽'}</h1>
}
//自定义方法————要用赋值语句的形式+箭头函数
changeWeather = ()=>{
//获取原来的isHot值
const isHot = this.state.isHot
//状态通过setState进行更新
this.setState({isHot:!isHot})
}
}
//2.渲染组件到页面
ReactDOM.render(<Weather/>,document.getElementById('test'))
1.3 在优化过程中遇到的问题总结
组件中的
render
方法中的this
为组件实例对象render()
的执行次数是1+n
(1 为初始化时的自动调用,n 为状态更新的次数)
组件自定义方法中由于开启了严格模式,
this
指向undefined
如何解决通过
bind
改变this
指向推荐采用箭头函数,箭头函数的
this
指向(✅开发常用方法)
state
使用注意事项- 使用的时候通过
this.state
调用state
里的值 - 在类式组件中定义
state
- 通过构造器初始化
state
- 推荐直接在类中添加属性
state
来初始化(✅开发常用方法)
- 通过构造器初始化
- 修改
state
不能直接修改,状态要通过setState()
进行更新 - 在执行
setState
操作后,React
会自动调用一次render()
- 使用的时候通过
⚠️注意:在类式组件的函数中,不能直接修改state
值
this.state.weather = '凉爽'
页面的渲染靠的是
render
函数
这时候会发现页面内容不会改变,原因是 React
中 state
不允许直接修改,而是通过类的原型对象上的方法 setState()
setState()
this.setState(partialState, [callback]);
partialState
: 需要更新的状态的部分对象callback
: 更新完状态后的回调函数
有两种写法
写法1:直接写更新的部分对象
this.setState({isHot: true})
写法2:传入一个函数,返回x需要修改成的对象,参数为当前的 state
// 传入一个函数,返回x需要修改成的对象,参数为当前的 state
this.setState(state => ({count: state.count+1});
setState
是一种合并操作,不是替换操作
2. props
与state
不同,state
是组件自身的状态,而props
则是外部传入的数据
2.1 props在类式组件中使用
需求: 自定义用来显示一个人员信息的组件
姓名必须指定,且为字符串类型;
性别为字符串类型,如果性别没有指定,默认为男
年龄为字符串类型,且为数字类型,默认值为18
- 首先创建类式组件Person
class Person extends React.Component{
render(){
//接收数据
const {name,age,sex} = this.props
//显示数据
return (
<ul>
<li>姓名:{name}</li>
<li>性别:{sex}</li>
<li>年龄:{age+1}</li>
</ul>
)
}
}
- 引入prop-types库,用于对组件标签属性进行限制
<!-- 引入prop-types,用于对组件标签属性进行限制 -->
<script type="text/javascript" src="../js/prop-types.js"></script>
对标签属性进行类型、必要性的限制
Person.propTypes = {
name:PropTypes.string.isRequired, //限制name必传,且为字符串
sex:PropTypes.string,//限制sex为字符串
age:PropTypes.number//限制age为数值
}
如果有函数需要限制,用PropTypes.func
speak:PropTypes.func//限制speak为函数
- 指定默认标签属性值
Person.defaultProps = {
sex:'男',//sex默认值为男
age:18 //age默认值为18
}
- 渲染组件到页面
ReactDOM.render(<Person name="Milo"/>,document.getElementById('test'))
总结(标准写法完整代码)
class Person extends React.Component{
render(){
//接收数据
const {name,age,sex} = this.props
//显示数据
return (
<ul>
<li>姓名:{name}</li>
<li>性别:{sex}</li>
<li>年龄:{age+1}</li>
</ul>
)
}
}
Person.propTypes = {
name:PropTypes.string.isRequired, //限制name必传,且为字符串
sex:PropTypes.string,//限制sex为字符串
age:PropTypes.number//限制age为数值
}
Person.defaultProps = {
sex:'男',//sex默认值为男
age:18 //age默认值为18
}
ReactDOM.render(<Person name="Milo"/>,document.getElementById('test'))
2.2 简写方式(开发常用)
- 用
propTypes
和defaultProps
两个属性来操作props
的规范和默认值时,可以使用static
直接添加在类里面,(类式组件的原型对象上)
//创建组件
class Person extends React.Component{
//对标签属性进行类型、必要性的限制
static propTypes = {
name:PropTypes.string.isRequired, //限制name必传,且为字符串
sex:PropTypes.string,//限制sex为字符串
age:PropTypes.number,//限制age为数值
}
//指定默认标签属性值
static defaultProps = {
sex:'男',//sex默认值为男
age:18 //age默认值为18
}
render(){
const {name,age,sex} = this.props
return (
<ul>
<li>姓名:{name}</li>
<li>性别:{sex}</li>
<li>年龄:{age+1}</li>
</ul>
)
}
}
//渲染组件到页面
ReactDOM.render(<Person name="Milo"/>,document.getElementById('test'))
2.3 props在类式组件总结
在使用的时候可以通过 this.props
来获取值 类式组件的 props
:
通过在组件标签上传递值,在组件中就可以获取到所传递的值
在构造器里的
props
参数里可以获取到props
constructor(props){ super(props) console.log('constructor',this.props); }
- 构造器是否接收props,是否传递给super,取决于:是否希望在构造器中通过this访问props
- 构造器可以不写
用
propTypes
和defaultProps
两个属性来操作props
的规范和默认值时,可以使用static
直接添加在类里面,(类式组件的原型对象上)可以通过
...
展开运算符来简化
//渲染组件到页面
const p = {name:'老刘',age:18,sex:'女'}
ReactDOM.render(<Person {...p}/>,document.getElementById('test'))
2.4 props在函数式组件中使用
函数在使用props的时候,是作为参数进行使用的(props)
- 创建函数式组件Person
function Person (props){
const {name,age,sex} = props
return (
<ul>
<li>姓名:{name}</li>
<li>性别:{sex}</li>
<li>年龄:{age}</li>
</ul>
)
}
- 对组件标签属性进行限制和设置默认值
Person.propTypes = {
name:PropTypes.string.isRequired, //限制name必传,且为字符串
sex:PropTypes.string,//限制sex为字符串
age:PropTypes.number//限制age为数值
}
Person.defaultProps = {
sex:'男',//sex默认值为男
age:18 //age默认值为18
}
- 在函数式组件中使用
propTypes
和defaultProps
两个属性来操作props
的规范和默认值时,只能放在函数外面定义
- 渲染组件到页面
ReactDOM.render(<Person name="Milo"/>,document.getElementById('test'))
2.5 props在函数式式组件总结
函数组件的 props
定义:
- 在组件标签中传递
props
的值 - 组件函数的参数为
props
- 在函数式组件中使用
propTypes
和defaultProps
两个属性来操作props
的规范和默认值时,只能放在函数外面定义(也是原型对象)
3. refs
Refs 提供了一种方式,允许我们访问 DOM 节点或在 render
方法中创建的 React 元素。
在我们正常的操作节点时,需要采用DOM API 来查找元素,但是这样违背了 React 的理念,因此有了
refs
有三种操作refs
的方法,分别为:
- 字符串形式
- 回调形式
createRef
形式
需求: 自定义组件,功能说明如下:
点击按钮, 提示第一个输入框中的值
当第2个输入框失去焦点时, 提示这个输入框中的值
第一步:创建类式组件(第一步三种方法都一样)
//创建组件
class Demo extends React.Component{
render(){
return(
<div>
<input type="text" placeholder="点击按钮提示数据"/>
<button>点我提示左侧的数据</button>
<input type="text" placeholder="失去焦点提示数据"/>
</div>
)
}
}
3.1 字符串形式refs
先给DOM元素添加ref属性,这里使用ref="input1"
的方式,让showData等功能使用时,从this.refs
获取input
标签。
<input ref="input1" type="text" placeholder="点击按钮提示数据"/>
<button onClick={this.showData}>点我提示左侧的数据</button>
<input ref="input2" onBlur={this.showData2} type="text" placeholder="失去焦点提示数据"/>
编写按钮功能和失去焦点功能,并使用字符串形式this.refs
获取input
标签
//展示左侧输入框的数据
showData = ()=>{
const {input1} = this.refs
alert(input1.value)
}
//展示右侧输入框的数据
showData2 = ()=>{
const {input2} = this.refs
alert(input2.value)
}
3.2 回调形式refs
(开发常用)
先给DOM元素添加ref属性,组件实例的ref
属性传递一个回调函数c => this.input1 = c
(箭头函数简写),这样会在实例的属性中存储对DOM节点的引用,使用时可直接从this
拿,或者this.input1
来使用
<input ref={c => this.input1 = c } type="text" placeholder="点击按钮提示数据"/>
<button onClick={this.showData}>点我提示左侧的数据</button>
<input onBlur={this.showData2} ref={c => this.input2 = c } type="text" placeholder="失去焦点提示数据"/>
c
会接收到当前节点作为参数,ref
的值为函数的返回值,也就是this.input1 = c
,因此是给实例下的input1
赋值
编写按钮功能和失去焦点功能,并使用字符串形式refs
获取input
标签
//展示左侧输入框的数据
showData = ()=>{
const {input1} = this
alert(input1.value)
}
//展示右侧输入框的数据
showData2 = ()=>{
const {input2} = this
alert(input2.value)
}
缺点:
如果 ref
回调函数是以内联函数的方式定义的,在更新过程中它会被执行两次,第一次传入参数 null
,然后第二次会传入参数 DOM 元素。这是因为在每次渲染时会创建一个新的函数实例,所以 React 清空旧的 ref 并且设置新的。
解决方法:
通过将 ref 的回调函数定义成 class 的绑定函数的方式可以避免上述问题,但是大多数情况下它是无关紧要的。
先给DOM元素添加ref属性,ref={this.saveInput},该方式可以避免内联函数的方式,在更新过程会执行两次的问题,但是大多数情况下它是无关紧要的。
<input ref={this.saveInput} type="text"/>
在类式组件中写一个saveInput的函数,这样在实例的属性中存储对DOM节点的引用,使用时直接从this
拿,或者this.input1
来使用
saveInput = (c)=>{
this.input1 = c;
}
3.3 createRef的refs
(推荐写法)
React 给我们提供了一个相应的API,它会自动的将该 DOM 元素放入实例对象中
我们先给DOM元素添加ref属性
<input ref={this.myRef} type="text" placeholder="点击按钮提示数据"/>
<button onClick={this.showData}>点我提示左侧的数据</button>
<input onBlur={this.showData2} ref={this.myRef2} type="text" placeholder="失去焦点提示数据"/>
天禹的理解:React.createRef调用后可以返回一个容器,该容器可以存储被ref所标识的节点,该容器是“专人专用”的
通过API,创建React的容器,会将DOM元素赋值给实例对象的名称为容器的属性的current
,所以后面调用要从current里拿
MyRef = React.createRef();
MyRef2 = React.createRef();
注意:专人专用,好烦,一个节点创建一个容器
编写按钮功能和失去焦点功能,并使用this.myRef.current获取input标签中的内容
//展示左侧输入框的数据
showData = ()=>{
alert(this.myRef.current.value);
}
//展示右侧输入框的数据
showData2 = ()=>{
alert(this.myRef2.current.value);
}
注意:我们不要过度的使用 ref,如果发生时间的元素刚好是需要操作的元素,就可以使用事件对象去替代。过度使用有什么问题我也不清楚,可能有 bug 吧
4. 事件处理
- React 使用的是自定义事件,而不是原生的 DOM 事件(为了更好的兼容性)
- React 的事件是通过事件委托方式处理的(委托给组件最外层的元素)(为了更加的高效)
- 可以通过事件的
event.target
获取发生的 DOM 元素对象,可以尽量减少refs
的使用
先不使用ref
<input onBlur={this.showData2} type="text" placeholder="失去焦点提示数据"/>
通过事件的 event.target
获取发生的 DOM 元素对象
//展示右侧输入框的数据
showData2 = (event)=>{
alert(event.target.value);
}
我的理解:
当发生事件的元素正好是要操作的元素,可以通过事件的 event.target
获取发生的 DOM 元素对象!
5. 非受控组件与受控组件
**需求: **定义一个包含表单的组件
输入用户名密码后, 点击登录提示输入信息
第一步:创建类式组件Login(第一步都一样)
//创建组件
class Login extends React.Component{
render(){
return(
<form onSubmit={this.handleSubmit}>
用户名:<input type="text" name="username"/>
密码:<input type="password" name="password"/>
<button>登录</button>
</form>
)
}
}
这里不在button
里用onClick
事件的方式,而是在form
里用onSubmit
,触发表单提交时调用回调函数
handleSubmit = (event)=>{
event.preventDefault() //阻止表单提交
alert(`你输入的用户名是:XXX,你输入的密码是:XXX`)
}
用event.preventDefault() 可以阻止表单提交
5.1 非受控组件
先给DOM元素添加ref属性,这里用ref的内敛回调函数形式
<form onSubmit={this.handleSubmit}>
用户名:<input ref={c => this.username = c} type="text" name="username"/>
密码:<input ref={c => this.password = c} type="password" name="password"/>
<button>登录</button>
</form>
这样会在实例的属性中存储对DOM节点的引用,使用时可直接从this
拿
handleSubmit = (event)=>{
event.preventDefault() //阻止表单提交
const {username,password} = this
alert(`你输入的用户名是:${username.value},你输入的密码是:${password.value}`)
}
5.2 受控组件
使用onChange事件,只要input发生改变(比如输入内容),就回调onChange的函数saveUsername
<form onSubmit={this.handleSubmit}>
用户名:<input onChange={this.saveUsername} type="text" name="username"/>
密码:<input onChange={this.savePassword} type="password" name="password"/>
<button>登录</button>
</form>
创建保存用户名和密码函数,通过事件的 event.target
获取发生事件的 DOM 元素对象,并使用setSate
保存到状态state
中
//保存用户名到状态中
saveUsername = (event)=>{
this.setState({username:event.target.value})
}
//保存密码到状态中
savePassword = (event)=>{
this.setState({password:event.target.value})
}
记得state初始化一下
//初始化状态
state = {
username:'', //用户名
password:'' //密码
}
这样会在state中存储用户和密码,使用时可直接从this.state
拿,记得拿到的是key-value键值对里的username,所以不要.value!!
//表单提交的回调
handleSubmit = (event)=>{
event.preventDefault() //阻止表单提交
const {username,password} = this.state
alert(`你输入的用户名是:${username},你输入的密码是:${password}`)
}
5.3 总结
非受控组件:现用现取就是非受控,使用了ref!
受控组件:随着你的输入,维护状态就是受控。类似Vue里的双向数据绑定。避免了使用ref!
三、高阶函数
关于这部分的知识,之前的笔记有记过了
链接Javascript高阶函数,关于AOP,偏函数,柯里化都有不错的记录
1 什么是高阶函数?
如果一个函数符合下面2个规范中的任何一个,那该函数就是高阶函数。
若A函数,接收的参数是一个函数,那么A就可以称之为高阶函数。
若A函数,调用的返回值依然是一个函数,那么A就可以称之为高阶函数。
常见的高阶函数有:Promise、setTimeout、arr.map()等等
2 函数的柯里化
函数的柯里化:通过函数调用继续返回函数的方式,实现多次接收参数最后统一处理的函数编码形式。
function sum(a){
return(b)=>{
return (c)=>{
return a+b+c
}
}
}
这里使用高阶函数中的函数柯里化对5.2 受控组件中的需求做优化
对onChange事件的回调函数this.saveUsername增加一个参数
<form onSubmit={this.handleSubmit}>
用户名:<input onChange={this.saveUsername('username')} type="text" name="username"/>
密码:<input onChange={this.savePassword('password')} type="password" name="password"/>
<button>登录</button>
</form>
注意:onChange事件里必须是一个函数,所以要对saveUsername函数进行柯里化,使得this.saveUsername('username')依然返回一个函数。
//保存表单数据到状态中
saveFormData = (dataType)=>{
return (event)=>{
this.setState({[dataType]:event.target.value})
}
}
注意:因为对象的属性名会默认转换成字符串类型,es6中的[]可以动态获取对象中的key值
所以这里从回调函数拿到dataType参数后,要用[dataType]的方式来使用
3 不用函数柯里化的实现
不用函数柯里化也可以实现,只要保证onChange事件里必须是一个函数即可,那么回调函数就可以改成
<form onSubmit={this.handleSubmit}>
用户名:<input onChange={event => this.saveFormData('username',event) } type="text" name="username"/>
密码:<input onChange={event => this.saveFormData('password',event) } type="password" name="password"/>
<button>登录</button>
</form>
onChange事件里箭头函数依然是一个函数
//保存表单数据到状态中
saveFormData = (dataType,event)=>{
this.setState({[dataType]:event.target.value})
}