一、组件
组件允许你将 UI 拆分为独立可复用的代码片段,并对每个片段进行独立构思。从概念上类似于 JavaScript 类或函数。它接受任意的入参(props),并返回用于描述页面展示内容的 React元素(jsx)
组件分为两种:函数组件、类组件
因为函数组件比较简单,在Recat官网中只有函数组件的教学,但是在公司的项目中类组件也会出现,也需要掌握
1.1 函数组件
- 函数组件(无状态组件):使用JS的函数创建组件
- 函数名称首字母必须以大写
- 函数组件必须有返回值(jsx)/null,表示该组件的结构,且内容必须有顶级元素
import React from 'react'
function App() {
return (
<div>这是第一个函数组件</div>
)
}
//或者使用箭头函数也可以
const App = () => {
return (
<div>这是第一个函数组件</div>
)
}
export default App
明确此函数只能是函数,不能当作类来使用,函数组件不能被new,因为函数有二义性,只能当作函数,不能当作类
创建函数组件有几点注意事项:
- react17之前,在定义组件时,一定要引入react类 import React from ‘react’,react17及之后,可以不用引入,因为react17之后,它在cli中进行了自动引入补全import React from ‘react’
- 定义函数,可以为箭头函数,推荐使用箭头函数
- 函数名称首字母大写
- 返回jsx结构,react18及之后它是允许返回undefined,之前是不允许的,返回null是可以的
- 通过esm导出对应的函数
- 如果你安装了jsx插件,就可以通过它的快速方式来创建函数组件 【rfc】,需要在vscode中安装JS JSX Snippets这个插件,插件中有很多快速创建组建的简写,如果想要安装,可以浏览这篇文章进行安装:http://t.csdnimg.cn/wwWrX
1.2 类组件
- 使用ES6语法的class创建的组件(状态组件)
- 类名称必须要大写字母开头
- 类组件要继承React.Component父类,从而可以使用父类中提供的方法或者属性
- 类组件必须提供 render 方法,用于页面结构渲染,结构必须要有顶级元素,且必须return去返回
- 如果你在vscode中安装了jsx插件,可以使用rcc来创建类组件
import React from 'react'
class App extends React.Component{
render(){
return (
<div>这是第一个类组件</div>
)
}
}
export default App;
//或者
import React, { Component } from 'react';
class App extends Component {
render() {
return (
<div>这是第一个类组件</div>
);
}
}
export default App;
1.3父子组件传值
为了方便理解,先写到一个文件里面,后期的项目就要拆开,模块化了
组件间传值,在React中是通过只读属性 props 来完成数据传递的。
props:接受任意的入参,并返回用于描述页面展示内容的 React元素。
props:中的数据是不能被修改的,只能读取。
props:是一个单向数据流。 父流向子,子不能直接去修改props中的数据
- 函数组件父子传值
import React from 'react'
//子组件
//父通过自定义属性向子组件传递数据,通过函数的形参来接受,一般起的形参的名称为props,它为一个对象{}
//在开发中,我们会写成下面这种解构方式,可以给默认值
const Child = ({ num = 200 }) => {
return (
<div>
<h3>Child组件: {num}</h3>
</div>
)
}
// 父组件
const App = () => {
let num = 100
return (
<div>
<h1>App组件</h1>
<hr />
{/* 注:在react中的jsx调用自定义组件时,组件名必须首字母大写开头 */}
{/* 父通过自定义属性向子组件传递数据 */}
<Child num={num} />
<Child />
</div>
)
}
export default App
如果是公用组件,不要把设置默认值的要求安排给开发者,目前的组件是自己用,可以这样写默认值
函数组件没有强制刷新方法,没有办法让视图更新,后续可以通过hook来更新,后续的笔记中会有说明,类组件有强制刷新的方法
- 类组件父子传值
import React, { Component } from 'react'
// 子组件
// 在子组件中,它是通过this.props来接受父组件通过自定义属性传入过来的数据
class Child extends Component {
render() {
// console.log(this.props)
// 解构,此写法也是在自定义给自己所用的组件中使用
const { num = 100 } = this.props
return (
<div>
<div>child组件 -- {num}</div>
</div>
)
}
}
// 父组件 -- 类组件它有this
class App extends Component {
// 类的成员属性,这样的写法是在es8版本之后才支持的
num = 200
//es8版本之前需要写在constructor里面
//constructor(){
//this.num=200
//}
render() {
return (
<div>
<h3>App组件 -- {this.num}</h3>
<hr />
<Child num={this.num} />
</div>
)
}
}
export default App
二、事件
2.1 事件绑定
- 类组件事件绑定
React 事件的命名采用小驼峰式,而不是纯小写
使用 JSX 语法时你需要传入一个函数作为事件处理函数,而不是一个字符串,函数或方法不能用小括号
类组件与函数组件绑定事件是差不多的,只是在类组件中绑定事件函数的时候需要用到this,代表指向当前的类的引用,在函数中不需要调用this
类组件可以强制更新视图,父子组建的render方法都执行了,更新完成之后会触发里面的回调函数
this.forceUpdate(() => {
console.log('更新完毕')
})
这种是不带参数
import React, { Component } from 'react'
// 子组件
class Child extends Component {
render() {
console.log('child --- render')
const { num = 100 } = this.props
return (
<div>
<div>child组件 -- {num}</div>
</div>
)
}
}
// 父组件
class App extends Component {
// 定义一个普通成员属性,它的变化不会触发视图更新
num = 200
// 定义成员方法,要用到箭头函数来定义,是为了解决this指向问题
addNum = () => {
this.num++
// 强制更新视图 -- 此方法只能在类组件中使用
this.forceUpdate(() => {
console.log('更新完毕')
})
}
render() {
return (
<div>
<h3>App组件</h3>
{/*
组当前button元素添加一个自定义的点击事件 onClick={绑定方法,它绑定是一个方法体}
它绑定方法中自动注入event事件对象
*/}
<button onClick={this.addNum}>+++++++</button>
<hr />
<Child num={this.num} />
</div>
)
}
}
export default App
这种是带参数
import React, { Component } from 'react'
// 子组件
// 在子组件中,它是通过this.props来接受父组件通过自定义属性传入过来的数据
class Child extends Component {
render() {
console.log('child --- render')
// 此写法也是在自定义给自己所用的组件中使用
const { num = 100 } = this.props
return (
<div>
<div>child组件 -- {num}</div>
</div>
)
}
}
// 父组件 -- 类组件它有this
class App extends Component {
// 定义一个普通成员属性,它的变化不会触发视图更新
num = 200
// 定义成员方法,要用到箭头函数来定义,是为了解决this指向问题
addNum = n => () => {
this.num += n
// 强制更新视图 -- 此方法只能在类组件中使用
this.forceUpdate(() => {
console.log('更新完毕')
})
}
render() {
console.log('app --- render')
return (
<div>
<h3>App组件 -- {this.num}</h3>
{/*
组当前button元素添加一个自定义的点击事件 onClick={绑定方法,它绑定是一个方法体}
它绑定方法中自动注入event事件对象
*/}
<button onClick={this.addNum(100)}>+++++++</button>
<hr />
<Child num={this.num} />
</div>
)
}
}
export default App
注意addNum的方法多了一个箭头,返回的是函数体或方法体就可以带参数了
- 函数组件事件绑定
这种不带参数
import React from 'react'
// 函数组件中绑定事件时没有this
const App = () => {
let num = 100
const addNum = () => {
num++
}
return (
<div>
<h1>App组件</h1>
<hr />
{/* 切记:默认绑定的事件方法一定不要写小括号 */}
<button onClick={addNum}>添加点击事件</button>
</div>
)
}
export default App
这种写法数据会变化,但是视图不会更新,后续会说如何更新视图
这种是带参数
import React from 'react'
// 函数组件中绑定事件时没有this
const App = () => {
let num = 100
const addNum = n => () => {
num += n
console.log(num)
}
return (
<div>
<h1>App组件</h1>
<hr />
{/*
绑定的是一个事件方法体,而不是执行的结果
如果在此处写了小括号,为了传数据,就需要你在绑定的方法返回一个函数
*/}
<button onClick={addNum(10)}>添加点击事件</button>
</div>
)
}
export default App
注意addNum的方法多了一个箭头,返回的是函数体或方法体就可以带参数了
2.2 事件合成
React合成事件是React 模拟原生DOM事件所有能力的一个事件对象。根据 W3C规范来定义合成事件,兼容所有浏览器,拥有与浏览器原生事件相同的接口。合成事件除了拥有和浏览器原生事件相同的接口,包括stopPropagetion()和preventDefault()。
在React中,所有事件都是合成的,不是原生DOM事件,可以通过 e.nativeEvent 属性获取原生DOM事件。合成事件不会映射到原生事件。
浏览器兼容,实现更好的跨平台
为什么出现这个技术?
**性能优化:**使用事件代理统一接收原生事件的触发,从而可以使得真实 DOM 上不用绑定事件。React 挟持事件触发可以知道用户触发了什么事件,是通过什么原生事件调用的真实事件。这样可以通过对原生事件的优先级定义进而确定真实事件的优先级,再进而可以确定真实事件内触发的更新是什么优先级,最终可以决定对应的更新应该在什么时机更新。
**分层设计:**解决跨平台问题,抹平浏览器差异。
import React, { Component } from "react";
// 在React17以前,都是委托给document容器的(而且只做了冒泡阶段的委托)
// 在React17及以后版本,都是委托给#root这个容器(捕获和冒泡都做了委托)只要挂载点的id不一样,可以做多入口,项目可以更复杂一些
class App extends Component {
// 挂载完毕后执行的方法 onMounted
componentDidMount() {
document.body.addEventListener(
"click",
function () {
console.log("原生 -- 捕获 -- body");
},
true
);
document.body.addEventListener(
"click",
function () {
console.log("原生 -- 冒泡 -- body");
},
false
);
document.querySelector("#root").addEventListener(
"click",
function () {
console.log("原生 -- 捕获 -- root");
},
true
);
document.querySelector("#root").addEventListener(
"click",
function () {
console.log("原生 -- 冒泡 -- root");
},
false
);
document.querySelector("#box").addEventListener(
"click",
function () {
console.log("原生 -- 捕获 -- box");
},
true
);
document.querySelector("#btn").addEventListener(
"click",
function (evt) {
console.log("原生 -- 捕获 -- button");
// 阻止事件链传递,它同元素中的已绑定的一样的事件会被触发
// evt.stopPropagation()
// 阻止事件链传递,它同元素中的已绑定的一样的事件不会被触发
evt.stopImmediatePropagation();
},
true
);
document.querySelector("#btn").addEventListener(
"click",
function (evt) {
console.log("原生 -- 捕获 -- button2");
},
true
);
document.querySelector("#box").addEventListener(
"click",
function () {
console.log("原生 -- 冒泡 -- box");
},
false
);
document.querySelector("#btn").addEventListener(
"click",
function () {
console.log("原生 -- 冒泡 -- button");
},
false
);
}
// ------------ 合成事件
onClickBoxCapture = (evt) => {
console.log("合成 -- 捕获 -- box");
// 阻止事件链传递
// evt.stopPropagation()
};
onClickBtnCapture = () => {
console.log("合成 -- 捕获 -- button");
};
onClickBox = () => {
console.log("合成 -- 冒泡 -- box");
};
onClickBtn = () => {
console.log("合成 -- 冒泡 -- button");
};
render() {
return (
<div>
<h1>App组件</h1>
<div
id="box"
onClickCapture={this.onClickBoxCapture}
onClick={this.onClickBox}
>
<button
id="btn"
onClickCapture={this.onClickBtnCapture}
onClick={this.onClickBtn}
>
点击事件
</button>
</div>
</div>
);
}
}
export default App;
阻止事件链传递,它同元素中的已绑定的一样的事件会被触发,evt.stopPropagation()
阻止事件链传递,它同元素中的已绑定的一样的事件不会被触发,evt.stopImmediatePropagation()
注意:在React17以前,都是委托给document容器的(而且只做了冒泡阶段的委托),不可以做多入口,在React17及以后版本,都是委托给#root这个容器(捕获和冒泡都做了委托)只要挂载点的id不一样,可以做多入口,项目可以更复杂一些
执行的顺序:捕获阶段,先合成在原生,冒泡阶段,先原生后合成
2.3 事件绑定的this问题
在类组件事件绑定中会有this指向的问题,需要通过解决this指向来完成对于数据处理
import React, { Component } from 'react'
// 1. bind()方法
// 2. 箭头函数
class App extends Component {
// 1.箭头函数
// handleClick = evt => {
// console.log(evt, this)
// }
// 2.箭头函数写在jsx中
// handleClick(evt, n) {
// console.log(evt, n, this)
// }
// 3.bind函数写在jsx中
// handleClick(n, evt) {
// console.log(evt, n, this)
// }
// 4.bind函数写在constructor中
// 如要你在子类中使用到自定义构造函数,则一定要调用一下父类中的构造函数 super()
// con 就可以有了
// 构造函数只会执行1次
constructor(props) {
super(props)
this.handleClick = this.handleClick.bind(this)
}
handleClick(evt) {
console.log(evt, this)
}
render() {
return (
<div>
{/* 1.自动把event事件对象注入到绑定的事件方法中 */}
{/* <button onClick={this.handleClick}>改变this指向</button> */}
{/* 2. 箭头函数写在jsx中,可以手动event和其它参数*/}
{/* <button onClick={evt => this.handleClick(evt, 100)}>改变this指向</button> */}
{/* 3.bind函数写在jsx中 bind不但可以改变this,还可以传参数 */}
{/* <button onClick={this.handleClick.bind(this, 200)}>改变this指向</button> */}
{/* */}
<button onClick={this.handleClick}>改变this指向</button>
</div>
)
}
}
export default App
三、补充
因为学习周期过长,我后面设置一个React学习的专栏,方便学习和管理笔记