react 02 - 组件
react中的组件定义方式有两种: 函数组件(无状态/UI组件) 类组件(状态组件/容器组件)
react组件一定要有返回值: 描述页面展示内容的React元素(jsx)
2.1 组件的创建方式
函数组件创建
函数组件本质: 就是一个函数
- 要满足一下要求:
- 函数名首字母要大写,因为jsx要求在组件调用的时候首字母要大写
- 函数组件可以接受一个参数,此参数可以是任意类型的数据,用来获取父组件的数据(父子组件传值细说)
- 函数必须返回一个jsx,并且jsx必须要有一个顶层元素包裹,也可以返回null, 18之后还可以返回undefined
- 定义好的函数一定要通过默认导出: export default 函数名
import React from 'react';
const AppFunction = () => {
return (
<div>
具体的页面内容
</div>
);
}
export default AppFunction;
安装插件: js jsx Snippets ===> rfc 可以快速创建模版代码
类组件创建
类组件: 一个类
- 要求:
- 只能使用es6中的类来定义类组件, 类名必须要大写
- 此类必须继承一个 React.Component 父类
- 此类中必须要重写 render方法,且此方法中必须要返回return一个 jsx/null/undefined
- 此类要导出,默认导出
import React from "react";
class App extends React.Component {
render(){
return (
<div>
<h3>class组件</h3>
</div>
)
}
}
export default App
快捷方式: rcc
函数具有二义性 : 在js中函数可以被当作构造函数通过new来调用,为了和类区分用new.target来判断是否是被new执行的
function Person(){
// 用new.target 来禁用函数被new, 只能当作函数来调用
if(new.target){
throw new Error('如果不是通过new执行的就返回undefined, 报次错误')
}
}
2.2 react父子组件传值
在react中一个函数一个类就是一个组件,区别与vue(一个文件就是一个组件),所以react中可以有多个组件
- 函数组件的父子间传值
- 单向数据流,只能父向子传值,父组件通过在调用子组件的时候添加自定义属性来传值
- 子组件接受props参数来获取父组件的传值, 可以直接接受props对象,也可以解构并赋予初值
- 子组件中props的值是只读的,不可修改
import React from 'react';
// const Child = (props) => { // 两种接受props的方式,一种可以直接接受整个props,但有个问题 ,在子组件中使用的时候,要通过对象调用的方式一个一个向外拿
const Child = ({title,num ='第二种:props是个对象,所以可以直接将里面的内容结构出来,还可以结构的同时进行赋初值'}) => {
// console.log(props)
return (
<div>
{/* <h2>子组件 --{props.title}</h2> */}
<h2>子组件 --{title} {num}</h2>
</div>
);
}
const AppProps01 = () => {
const title = '我是父组件传过来的标题'
const num = undefined
return (
<div>
<h2>父组件</h2>
{/*
父组件,他是通过自定义属性的方式向子组件传递数据‘props’
props是只读属性并且是单向数据流,只能父传递给子 ,子只能读取值,不能修改值
*/}
{/* 调用自组建 */}
<Child title={title} num={num}/>
</div>
);
}
export default AppProps01;
- 类组件的父子间传值
- props单向数据流,父向子传值,只读属性
- 区别去函数组件中的传值方式: 类组件中的自定义属性传值时传递的是在类中定义的成员属性,并且一定要注意加this
- 子组件在接受父组件传过来的值时,在render函数中通过this.props成员属性来获取值,props依旧是对象形式的值,可以解构
import React, { Component } from 'react';
// 类组件间传值
class Child extends Component {
// 类组件中的子组件通过成员属性 this.props 来获取父组件中传过来的值
// this.props 是一个对象
render() {
// console.log(this.props)
const {title} = this.props
return (
<div>
<h2>class子组件 -- {title}</h2>
</div>
);
}
}
class AppClassProps01 extends Component {
// es8以后的成员属性,可以不用在construct中定义
title = 'class父类中的标题'
render() {
return (
<div>
<h2>class父组件</h2>
{/* 在类组件中,获取成员属性和成员方法时 ,一定要通过this来获取 */}
<Child title={this.title} />
</div>
);
}
}
export default AppClassProps01;
面试题: TS中已经可以用联合属性来解决问题问什么还要用多态?
2.3 react事件
react中的事件分为原生事件和合成事件: 原生就是通过addEventLisenter的js操作进行的,而合成事件: 直接定义在react中的事件会被react处理,语法上有一些不同
要求
- 事件的命名严格采用小驼峰命名法
- jsx语法中当要传入一个函数作为事件的处理函数时,不能加小括号
- 在使用类组件时,还要注意this的指向问题,函数组件中没有这个问题
2.3.1 函数组件
import React from 'react';
const clickHandler2 = () => ()=>{
console.log('webg == 我必搞')
}
//定义在组件函数的上面,const var let 没有限制
var clickHandler = () =>{
console.log('clickHandler')
}
const AppFunction01 = () => {
return (
<div>
{/*
1, 函数名用小驼峰
2, 绑定的事件方法不能加小括号,并且如果将执行的函数体放在jsx下面(组件函数的后面)的时候,一定要用function 来定义函数
3, 因为return会中断后面的代码执行,要用function来声明方法,是的变量提升并赋值
*/}
<button onClick={clickHandler}>点击触发事件</button>
{/* 如果想用小括号,那么事件绑定的是一个函数体就可以 */}
<button onClick={clickHandler2()}>小括号触发事件</button>
</div>
);
}
// function clickHandler(){
// console.log('clickHandler')
// }
// 小括号触发事件
// function clickHandler2(){
// return function(){
// console.log('wgb==我必搞')
// }
// }
// function clickHandler2(){
// return ()=>{
// console.log('wbg === 我必搞')
// }
// }
export default AppFunction01;
2.3.2 类组件
思考两个问题 1,this的指向问题 2,call、apply、bind的作用和用法都是什么
import React, { Component } from 'react';
class AppFunction02 extends Component {
clickHandle1(){
console.log('clickHandle1')
}
clickHandle3(){
return ()=>{
console.log('clcikHandler3')
}
}
num = 100
clickHandle4(){
return ()=>{
console.log( this.num ) // 这样不报错
}
// console.log(this.num) // 这样就报错了,为什么, 解决方案可以用bind来改变this指向,为社么可以用bind解决
}
clickHandle5(){
console.log(this.num)
}
render() {
return (
<div>
{/* 类组件中的方法定义,不用关注方法的书写顺序是在return前还是return后 */}
<button onClick={this.clickHandle1}>clickHandle1</button>
<button onClick={this.clickHandle2}>clickHandle2</button>
{/* 同样的如果要加小括号,可以用柯里化函数 */}
<button onClick={this.clickHandle3()}>clickHandler3</button>
{/* 类组件中有函数的调用有this指向问题 , */}
<button onClick={this.clickHandle4()}>this问题</button>
<button onClick={this.clickHandle5.bind(this)}>bind解决this指向问题</button>
</div>
);
}
clickHandle2(){
console.log('clickHandle2')
}
}
export default AppFunction02;
2.3.3合成事件
react将原生事件进行处理:将所有的事件都绑定到挂在节点,目的: 对事件进行统一代理,使dom上不用绑定事件也可以用react挟持事件触发来实现操作,进行性能优化 , 这样可以通过对原事件的优先级定义来确定真是事件的优先级,再进而确定真实事件内触发的更新是什么优先级,最终确定对应更新应在什么时机更新 , 可以解决跨平台问题,抹平浏览器差异
react16之前绑定到body上,缺点: 只能有一个委托节点
16之后委托到挂载节点元素中(root),可以有多个委托节点,如果有多入口的操作应用时,可以分开委托(root1 , root2 ,…)
事件的执行分为: 捕获阶段 --> 目标阶段 --> 冒泡阶段
以类来举例
import React, { Component } from "react";
class APPs extends React.Component {
click() {
console.log("合成-冒泡- click1")
}
clickCapture() {
console.log('合成-捕获-click2')
}
componentDidMount() {
// addEventListener("事件类型" , function(){事件函数} , boolean(true : 捕获阶段执行 / false: 冒泡阶段执行))
document.getElementById('btn').addEventListener('click', () => {
console.log('原生- 冒泡 - btnClick')
}, false)
document.getElementById('btn').addEventListener('click', () => {
console.log('原生 - 捕获 - btnClick')
}, true)
document.getElementById('root').addEventListener('click', () => {
console.log('原生 - 冒泡 - 顶层元素click')
} , false )
document.getElementById('root').addEventListener('click', () => {
console.log('原生 - 捕获 - 顶层元素click')
} , true )
document.body.addEventListener('click' , () => {
console.log('原生 - 冒泡 - bodyClick')
} ,false )
document.body.addEventListener('click' , ()=>{
console.log('原生 - 捕获 - bodyClick')
} , true)
}
render() {
return (
<div>
<button id="btn" onClick={this.click} onClickCapture={this.clickCapture} >click</button>
</div>
)
}
}
export default APPs
合成事件和原生事件相结合的具体执行顺序: body-原生-捕获 —> 目标元素上级各节点绑定合成事件的捕获(capture) —> root-原生-捕获 (这两个阶段可以看作委托的合成事件在挂载节点先执行,然后执行挂在节点的原生捕获) —> 目标元素-原生-捕获 —> 目标元素-原生-冒泡 —> 目标元素上级个节点绑定的合成事件的冒泡执行 —> root-原生-冒泡(捕获的回程冒泡)—> body-原生-冒泡
react中合成事件的冒泡函数就是正常默认的函数,捕获阶段的函数为: 莫某Capture事件
原生事件的绑定: addEventLisenter(‘事件类型’ , 事件函数 , true/false 控制冒泡/捕获执行) 默认为false=== 冒泡执行,true===捕获执行
2.3.4 事件阻止
event.stopPropergation() event.stopImmediatePropergation()
- 相同点: 都是用来阻止事件传播的
- 不同点: stopPropergation: 阻止当前元素之后的父级元素的冒泡事件执行,但是自身的其他事件的冒泡依旧可以执行, 而
stopImmediatePropergation
在js顺序执行到此事件时, 后面所有的事件都不在执行,包括当前元素自身的冒泡事件, 但书写顺序在此事件之前的冒泡事件不受影响(阻止当前元素中的未执行事件)
2.3.5合成事件模拟 — 实际就是做事件委托
react中为了减少元素增删时,相应绑定事件的销毁和再创建带来的性能损耗,将事件做成了合成事件,其实现原理如下
<div id="root">
<div id="box">
<div id="son" οnclick="clickHandler">
我是一个按钮
</div>
</div>
</div>
<script>
document.getElementById('root').addEventListener('click' , () => {
// event.target 事件的触发元素
console.log(event.target , '查看事件触发元素' , event.target.getAttribute('onclick') , '获得元素中的自定义属性')
// 一种事件委托的方式,将son元素上的事件在root元素上代为执行,
window[event.target.getAttribute('onclick')]()
} ,false)
function clickHandler(){
console.log('原生事件')
}
</ script>
这样在数据更新时,执行的事件,只会当做一个属性去获取, 在react 中要拿到原生事件:event.nativeEvent
再js中的function定义的函数,可以看做实在window对象中的添加的属性,属性名是函数名,值是函数体
function eventHandler(){
console.log('事件')
}
window['eventHandler']()
// 就类似
let obj = {
a : function b(){
console.log('shijian')
}
}
obj['a']() // 至于是否是你名函数不重要
this指向问题(仅存在于类组件)
问题: this的指向问题,细分为11类,可以在日常积累里面找,这里不做整理
// 在类组件中,需要解决一下this的指向问题,在函数组件中没有this问题
// 解决方案:
/*
1, 剪头函数 : 键头函数中的this是指向当前函数上下文环境中的this
2, bind
*/
Class AppThis extends Component {
num = 100
addNum1() {
this.num++
this.forceUpdate()
}
//键头函数解决this指向问题
addNum2 = () => {
this.num++
// 强制试图刷新,此方法只有类组件中有,函数组件要自己写方法实现
this.forceUpdate()
}
addNum3(){
this.num++
this.forceUpdate()
}
// 在类组件中如果重新定义了构造函数,一定要调用父类中的构造方法
constructor(props){
super(props)
this.addNum4 = this.addNum4.bind(this)
}
addNum4(){
this.num++
this.forceUpdate()
}
addNum5(num) {
this.num += num
this.forceUpdate()
}
addNum6 = n => event => {
console.log(n , event )
this.num += n
this.forceUpdate()
}
addNum7(n){
this.num += n
this.forceUpdate()
}
render() {
console.log('Rerender')
return (
<div>
<h3>{this.num}</h3>
{/* 1, 箭头函数获取正确的this */}
<button onClick={() => this.addNum1()}>++num1++</button>
<button onClick={this.addNum2}>++num2++</button>
{/* 2, 使用bind改变this */}
<button onClick={this.addNum3.bind(this)}>++num3++</button>
{/* 高性能写法 */}
<button onClick={this.addNum4}>++num4高性能++</button>
{/* 事件传入参数 */}
{/* 在react中绑定的方法都会自动注入一个event对象, 此方法的事件对象, 里面包含的是事件信息 */}
{/* 1, 还是用箭头函数 */}
<button onClick={evt => this.addNum5(10)}>++参数5++</button>
<button onClick={this.addNum6(100)}>++参数6++</button>
{/* 2, 使用bind传参 */}
<button onClick={this.addNum7.bind(this , 10)}>++num7++</button>
</div>
);
}
}