1. 对于组件的理解
官网定义:
组件,从概念上类似于 JavaScript 函数。它接受任意的入参(即 “props”),并返回用于描述页面展示内容的 React 元素,(虚拟DOM)。
组件通俗的理解就是JavaScript构造函数, 知识构造函数返回一个虚拟DOM
2. 组件的使用
2.1 组件分类
既然React组件就是构造函数,在JS中构造函数有两种写法,ES5的构造函数和ES6的类
因此组件也分为两种
- 函数组件,就是ES5的构造函数, 通常称为无状态组件
- class组件,就是ES6的类,通常继承React.Component, 为有状态的组件
至于什么是状态,状态就是state,后面再聊
2.2 组件的基本使用
注意,组件名必须大写,如果小写会被react认为是原生的DOM元素
示例说明:
- 函数组件的效率要高于Class组件,原因在于函数组件直接调用.class组件还要实例化
- 但是函数是无状态组件,如果需要定义有状态的组件(复杂组件),需要使用Class组件
- Class组件中,必须实现render函数, 返回React元素(虚拟DOM)
3. 组件的嵌套使用
说明:
- 组件的牵头使用, 就是在一个组件的React元素中,可以任意使用其他组件
- 组件在使用时可以多次复用
示例代码:
// 组件的嵌套使用
// 1. 定义子组件
function MyList(){
return (
- 苹果
- 香蕉
- 梨子
)
}
// 2. 父组件
class MyCom extends React.Component{
render(){
return (
父组件
{/* 组件复用: 注意JSX语法中注释写在{}里 */}
)
}
}
// 将组件渲染到页面上
ReactDOM.render( , document.getElementById("app"))
4. 组件中的事件
React元素的事件处理和DOM元素很相似, 但是语法上有所不同
4.1 React元素事件与DOM元素的不同
- React事件命名采用小驼峰(camelCase),而不是纯小写.例如
onClick
- 使用JSX语法时,需要传入一个函数作为事件处理函数,而不是一个字符串
示例代码如下
点击
点击
4.2 事件的使用
4.2.1 函数组件中绑定事件
示例代码如下:
let flag = false
function MyCom(){
function handleClick(){
console.log(11);
flag = !flag
// 调用render重新渲染
render();
}
return { flag ? "天王盖地虎" : "小鸡炖蘑菇"}
}
// 渲染方式一
// ReactDOM.render( , document.getElementById("app"))
// 渲染方式二
// 将React.render渲染函数封装
function render(){
ReactDOM.render( , document.getElementById("app"))
}
render()
示例代码说明:
- 其实函数组件没有状态,我们只能利用JS变量模拟状态
- 当示例中点击事件发生后,
flag
变量值会被改变, - 但是如果采用渲染方式一,页面不会发生改变, 因为变量的改变不会触发React重新渲染
- 因此我们就需要采用第二种方法,将渲染函数封装, 在事件触发后手动的触发封装函数,重新渲染
4.2.2 class组件中绑定事件
示例代码如下:
class MyCom extends React.Component{
constructor(){
super();
this.state = {
flag : true
}
}
handleClick(){
console.log(111);
this.setState({
flag: !this.state.flag
})
}
render(){
return { this.state.flag ? "天王盖地虎" : "小鸡炖蘑菇"}
}
}
ReactDOM.render( , document.getElementById("app"))
示例代码说明:
- class组件中定义了state状态
- 状态中的flag在通过
setState
方法改变后,会自动触发React重新渲染 - 因此这里我们就需要在将
ReactDOM.render
方法封装了,因为状态的改变会触发React重新渲染 - 在class组件中会看到大量使用this,因为React会实例化组件对象,通过组件对象调用内部方法
4.3 class组件中事件处理函数的this指向问题
采用普通定义事件处理函数的方式,并且普通的调用,来查看this指向问题
实例代码:
class MyCom extends React.Component{
constructor(){
super();
}
handleClick(){
console.log(this); // window
}
render(){
return 点击
}
}
ReactDOM.render( , document.getElementById("app"))
实例说明:
- 如果你测试过代码就会了解,此时的事件处理函数中的this是window
- 丢失this原因在于React元素会被编译为
React.createElement
虚拟DOM,就丢失了this作用域 - 如果在函数中不使用任何组件内部的状态或其他方法,这样写没有任何问题
- 可是如果你希望在事件处理函数中能够获取到组件实例中的方法或状态,就需要使用到this
4.4 如何解决this指向
解决this指向主要在两大方向思考对策
- 在事件处理函数绑定时
- 在事件处理函数定义时
无论是在绑定事件处理函数还是在定义事件处理函数都有多个方法
4.4.1 绑定事件函数时通过bind修改this
示例代码如下
class MyCom extends React.Component{
constructor(){
super();
}
handleClick(){
console.log(this);
}
render(){
/* bind强制this绑定 */
return 点击
}
}
ReactDOM.render( , document.getElementById("app"))
示例说明:
- 在绑定事件处理函数时通过bind方法强制修改函数内部this指向,为当前的this.
- 当前绑定的this就是组件实例
4.4.2 绑定箭头函数返回事件处理函数的自执行
示例代码如下:
class MyCom extends React.Component{
constructor(){
super();
}
handleClick(){
console.log(this);
}
render(){
/* 箭头函数 */
return this.handleClick() }>点击
}
}
ReactDOM.render( , document.getElementById("app"))
示例说明:
- 其实这里绑定的事件处理函数是箭头函数
- 在箭头函数中执行需要处理逻辑的函数,如
this.handleClick()
- 此时函数
handleClick
的this 就是指向当前调用的this
- 当前调用
handleClick
的this就是组件实例对象,
4.4.3 将事件处理函数绑定给实例对象自己的属性
实例代码如下:
class MyCom extends React.Component{
constructor(){
super();
// 这句代码的意思,是将原型上的方法强制绑定this以后赋值给实例对象自己
this.handleClick = this.handleClick.bind(this)
}
// 这个方法是定义在实例原型对象上的
handleClick(){
console.log(this);
}
render(){
// 这里this.handleClick 会优先绑定组件实例自己的方法
return 点击
}
}
ReactDOM.render( , document.getElementById("app"))
实例说明:
- 原理绑定是事件是原型上的方法
- 通过将原型方法赋值给实例自己的属性并强制修改方法内部this以后
- 在绑定事件处理函数时,就会优先绑定组件实例自己的方法,
- 那么this也就重新执行组件实例
- 但是也有不好的地方,就是组件实例和原型上都有相同方法,原型上还不用
4.4.4 使用箭头函数定义事件处理函数
实例代码:
class MyCom extends React.Component{
constructor(){
super();
}
// 使用箭头函数定义事件处理函数
handleClick = () => {
console.log(this);
}
render(){
return 点击
}
}
ReactDOM.render( , document.getElementById("app"))
实例说明:
- 采用箭头函数定义事件处理函数,
- 这样事件处理函数本事将没有this
- 事件处理函数的this将只想父作用域的this
4.5 事件对象
事件对象主要看是如何绑定的, 分为两种
- 直接绑定事件处理函数
- 通过bind绑定事件处理函数
- 通过绑定箭头函数,在箭头函数中执行之前需要处理的函数
4.5.1 直接绑定事件处理函数
示例代码:
class MyCom extends React.Component{
//...
handleClick = (ev) => {
console.log(ev); // class事件对象
console.log(event) // 原生事件对象
}
render(){
// 这里this.handleClick 会优先绑定组件实例自己的方法
return 点击
}
}
示例说明:
- 直接绑定事件处理函数, 那么事件对象默认会是第一个参数
- 这种绑定方式没办法给事件处理函数传递额外的参数
4.5.2 通过bind绑定事件处理函数
示例代码:
class MyCom extends React.Component{
constructor(){
super();
}
// 这个方法是定义在实例原型对象上的
handleClick(num,ev){
console.log(ev); // class事件对象
console.log(event); // 原生事件对象
console.log(num); // 20
console.log(this); // 组件实例对象
}
render(){
return 点击
}
}
示例说明:
- 通过bind绑定的事件对象,可以传递额外的参数
- 在事件处理函数中,所有的额外参数接受完毕以后,最后还有一个形参就是事件对象
4.5.3 箭头函数绑定事件处理函数
示例代码如下
class MyCom extends React.Component{
constructor(){
super();
// 这句代码的意思,是将原型上的方法强制绑定this以后赋值给实例对象自己
}
// 这个方法是定义在实例原型对象上的
handleClick(ev,num){
console.log(ev); // class事件对象
console.log(event); // 原生事件对象
console.log(num); // 20
console.log(this); // 组件实例对象
}
render(){
// 这里this.handleClick 会优先绑定组件实例自己的方法
return {
this.handleClick(ev,20)
}}>点击
}
}
示例说明:
- 此时的事件处理函数严格的来说应该是手动绑定的箭头函数
- 因此事件对象就是箭头函数的第一个参数
- 真正需要执行逻辑的函数需要手动调用
- 因此事件对象也需要手动的传递