组件
创建组件的两种方式
函数组件
函数组件:使用JS的函数或者箭头函数创建的组件
- 为了区分普通标签,函数组件的名称必须
大写字母开头
- 函数组件必须有返回值,返回一段JSX,表示该组件的结构
- 如果组件不渲染任何内容,可以返回
null
。不能返回undefined
。在17版本返回undefined
会报错,18版本修改了不报错了。 React
组件对大小写敏感,使用组件同样要使用大写单词开头。
function Hello() {
return <div>刀刀</div>
}
const title = (
<>
<Hello></Hello>
</>
)
执行了 ReactDOM.render
做了什么:
- 解析标签,找到相应组件,找不到则报错
- 发现组件是函数定义的,调用该函数,将返回的虚拟dom转为真实dom,随后呈现页面
类组件
创建语法如下:
class Xxxx extends React.Component{
render() {
return
}
}
注意:
- 类组件的名称必须是大写字母开头,使用上大小写敏感
- 类组件应该继承
React.Component
父类,从而可以使用父类中提供的方法或者属性- 类组件必须提供
render
方法,并返回组件的结构,如无需要返回的结构,返回null
class Hello extends React.Component{
render() {
return <div>刀刀好棒</div>
}
}
ReactDOM.render(<Hello/>, document.getElementById('root'))
执行了 ReactDOM.render
做了什么:
- 解析标签,找到相应组件,找不到则报错
- 发现组件是类定义的,随后new出该类的实例,并通过该类的实例,调用到原型上的render方法,将返回的虚拟dom转为真实dom,随后呈现页面
将组件提取到单独的js文件中
实现方式:
- 创建Hello.js
- 创建组件(函数 或 类)
- 在 Hello.js 中导出该组件
- 在 index.js 中导入 Hello 组件
- 渲染组件
子组件中:
import { Component } from "react";
export class Hello extends Component {
render() {
return <div>刀刀</div>
}
}
父组件中:
import React from "react";
import ReactDOM from "react-dom";
import {Hello} from "./Hello";
ReactDOM.render(<Hello/>, document.getElementById('root'))
类组件的状态
-
状态
state
即数据。(同Vue的data)- 类似Vue中的data函数。
- React同样是数据驱动视图 更新UI。
-
状态
state
是组件内部的私有数据
,独立作用域,(同Vue的data) -
状态 state 的值是对象,表示一个组件中可以有多个数据
-
声明
class
组件class App extends React.Component { render() { return <h1>{}</h1> } }
-
声明
state
数据class App extends React.Component { state = { count :100 } render() { return <h1>{}</h1> } }
-
获取
state
class App extends React.Component { state = { count :100 } render() { return <h1>{this.state.count}</h1> } }
总结:
- state的作用:声明数据。 React中同样是数据驱动视图,数据变化导致UI更新。
- 声明方式:
state = {}
是固定的写法,独立作用域。 - 使用方式:通过
this.state.属性名
的方式来访问 - this指向组件实例对象。
拓展:类与继承
class 基本语法
在 ES6 之前通过构造函数创建对象,在 ES6 中新增了一个关键字 class, 类 和构造函数类似,用于创建对象。
类与对象的区别:
-
类:指的是一类的事物,是个概念,比如车 手机 水杯等
-
对象:一个具体的事物,有具体的特征和行为,比如一个手机,我的手机等, 类可以创建出来对象。
-
类创建对象的基本语法
- 基本语法
class 类名{ }
- 构造函数
constructor
的用法,创建对象 - 在类中提供方法,直接提供即可
- 在类中不需要使用分号,分隔(Vue因为是对象才需要分号分隔)
- 基本语法
extends 实现继承
- extends 基本使用
- 类可以使用它继承的类中所有的成员(属性和方法)
- 类中可以提供自己的属性和方法
- 注意:如果想要给类中新增属性,必须先调用 super 方法
组件类型
- 无状态组件 : 早期,函数组件是不能自己提供数据【前提:基于hooks之前说的】
- 有状态组件 : 类组件可以自己提供数据,组件内部的状态(数据如果发生了改变,内容会自动的更新),数据驱动视图
状态组件 - 容器组件
请求的数据保存在组件内。
无状态组件 - 展示组件
负责样式与结构的渲染。
组件实例的三大核心属性
状态
声明状态
class Weather extends React.Component {
constructor(props) {
super(props) // 继承父类的属性,必须要写,不然报错
this.state = {isHot: true}
}
render() {
console.log(this) // Weather所有的属性,其中state对象内有一个isHot属性
return <h1>今天天气很 {this.state.isHot ? '炎热' : '凉爽'}</h1>
}
}
ReactDOM.render(<Weather/>, document.getElementById('test'))
事件绑定
- react把原生的事件都重写了一遍,事件要大写,如:onClick、onBlur
- 事件绑定右边要给一个函数,用花括号引起来,而且不能加括号
class Weather extends React.Component {
constructor(props) {
super(props) // 继承父类的属性,必须要写,不然报错
this.state = {isHot: true}
this.newDemo = this.demo.bind(this) // 这里的bind有两个作用
}
render() {
console.log(this) // Weather所有的属性,其中state对象内有一个isHot属性
// 函数demo已经在实例上可以调用,因此使用this来调用
return <h1 onClick={this.demo}>今天天气很 {this.state.isHot ? '炎热' : '凉爽'}</h1>
}
// 把demo放在原型对象上,实例使用
demo() {
// demo是作为onClick的回调,所以不是通过实例调用的,而是直接调用。类中的方法默认开启局部的严格模式,this指向undefined
console.log(this.state.isHot);
}
}
ReactDOM.render(<Weather/>, document.getElementById('test'))
bind
的作用:
- 返回一个新的函数,此时实例身上生成一个
newDemo
的函数。- 修改
this
指向,新函数的this
指向函数demo
。
setState
状态 state
不可直接更改,需要使用 setState
配合更改。
class Weather extends React.Component {
constructor(props) {
super(props)
this.state = {isHot: true}
this.newDemo = this.demo.bind(this)
}
render() {
return <h1 onClick={this.demo}>今天天气很 {this.state.isHot ? '炎热' : '凉爽'}</h1>
}
demo() {
const isHot = this.state.isHot
this.setState({isHot: !isHot})
}
}
ReactDOM.render(<Weather/>, document.getElementById('test'))
总结:
setState
是实质上合并,而不是替换。找到需要修改的地方修改新的值,合并其他不需要修改的值。- 在
setState
修改时,constructor
只渲染一次,render
渲染 1+n 次(1是一开始渲染,n是修改了n次)。- 方法
demo
触发几次调用几次。
简写方式
一开始写构造器是为了初始化 state
状态,在类中,可以直接写 a=1
的形式来初始化赋值,因此可以省去构造器部分。事件也是一样,使用赋值的形式,把函数改为箭头函数。
class Weather extends React.Component {
// 初始化状态
this.state = {isHot: true}
render() {
return <h1 onClick={this.demo}>今天天气很 {this.state.isHot ? '炎热' : '凉爽'}</h1>
}
// 自定义事件
demo = () => {
const isHot = this.state.isHot
this.setState({isHot: !isHot})
}
}
ReactDOM.render(<Weather/>, document.getElementById('test'))
总结:
- 在构造器外部使用赋值的形式声明变量,可以省略构造器部分的代码。
- 箭头函数没有
this
指向,会去其外一层寻找this
作为自己的指向。本案例函数demo
的this
指向了类Weather
。
props
基本使用
class Person extends React.Component {
render() {
const {name,age,sex} = this.props // 获取到给的自定义属性
return (
<ul>
<li>{name}</li>
<li>{age}</li>
<li>{sex}</li>
</ul>
)
}
}
ReactDOM.render(<Person name="daodao" age="22" sex="男"/>,document.getElementById('test'))
批量传递
使用展开运算符来获取所有数据并赋值过去。
class Person extends React.Component {
render() {
const {name,age,sex} = this.props // 获取到给的自定义属性
return (
<ul>
<li>{name}</li>
<li>{age}</li>
<li>{sex}</li>
</ul>
)
}
}
const p = {name: 'daodao', age: '21', sex: '男'}
ReactDOM.render(<Person {...p}/>,document.getElementById('test'))
添加限制
- 某项数据必传
Person.propTypes = {
name: PropTypes.isRequired
}
- 数据类型限制
Person.propTypes = {
name: PropTypes.func
}
- 不传给默认值
Person.defaultProps = {
name: 'daodao'
}
propTypes
是规则方法,必须这么写,写了之后 react
才会添加规则。React
有一个内置方法 PropTypes
,用于限制属性。
但是这么写会把 React
这个包体积变得很大,因此需要额外引包,直接使用即可。
简写模式
class Person extends React.Component {
static propTypes = {
name: PropTypes.func
}
static defaultProps = {
name: 'daodao'
}
render() {
}
}
props与构造器
构造器是否接收props,是否传递super,取决于:是否希望在构造器中通过this来访问props
函数式组件使用props
函数式组件,本质上用函数的形参来接收数据,没有this指向。通过 函数名.propTypes
的方式设置类型等。
function Person(props) {
const {name,age,sex} = props // 获取到给的自定义属性
return (
<ul>
<li>{name}</li>
<li>{age}</li>
<li>{sex}</li>
</ul>
)
}
Person.propTypes = {
name: PropTypes.func
}
ReactDOM.render(<Person name="daodao" age="22" sex="男"/>,document.getElementById('test'))
refs
组件内的标签可以使用ref来标识自己。
字符串类型的ref
目前已弃用 this.refs.ref名称
的形式,如果写多了会有效率的问题。
回调类型的ref
为 ref
赋值一个箭头函数,该箭头函数的this指向就是实例对象,有一个形参,获取到该ref的dom节点。通过 this.变量名
在实例对象中创建一个变量存储这个节点。
class Person extends React.Component {
render() {
return (
<input ref={v => this.input = v} />
)
}
}
调用次数上,如果回调函数是以上方案例中内联函数的方式定义的,在更新的过程中会执行两次,第一次传入的参数是null,第二次传递的才是想要的节点数据。
class Person extends React.Component {
saveInput = (v) => {
this.input = v
}
render() {
return (
<input ref={this.saveInput} />
)
}
}
createRef
React.createRef
调用后可以返回一个容器,该容器可以存储被 ref
所标识的节点,该容器是专人专用,一个只能存储一个节点。
class Person extends React.Component {
myRef = React.createRef()
saveInput = () => {
console.log(this.myRef.current.value)
}
render() {
return (
<input onClick={this.saveInput} ref={this.myRef} />
)
}
}
事件
事件处理
注册事件
语法on+事件名 ={事件处理程序
比如 onClick={this.handleClick}
-
少量代码:使用一个箭头函数
class App extends React.Component { render() { return <button onClick = {() => alert(123)}>点我</button> } }
-
多行代码:自定义一个函数
class App extends React.Component { render() { return <button onClick = {this.handlerClick}>点我</button> } handlerClick() { console.log('chao'); console.log('tie'); console.log('ze'); } }
总结:
- React事件采用驼峰命名法,比如
onMouseEnter
,onClick
- class中自定义方法,通过
this.方法名
。
常见错误
-
函数后加括号
class App extends React.Component { render() { return <button onClick = {this.handlerClick()}>点我</button> } handlerClick() { console.log('chao'); console.log('tie'); console.log('ze'); } }
结果是立即执行,没有等待点击事件触发。
-
省略大括号
class App extends React.Component { render() { return <button onClick = 'alert(123)'>点我</button> } }
获取事件对象
默认形参就是事件对象。
class App extends React.Component {
render() {
return <a href="www.baidu.com" onClick = {(e) => e.preventDefault()}>点我</a>
}
}
class App extends React.Component {
render() {
return <a href="www.baidu.com" onClick = {this.handlerClick}>点我</a>
}
handlerClick(e) {
e.preventDefault()
}
}
事件传值
方法:包一层箭头函数,在箭头函数中,调用处理函数。
class App extends React.Component {
render() {
return <button onClick = {(e) => this.handlerClick(e, 2)}>点我</button>
}
handlerClick(e, num) {
console.log(e, num);
}
}
this指向
class App extends React.Component {
render() {
return <button onClick = {this.handlerClick}>点我</button>
}
handlerClick() {
console.log(this);
}
}
最终 this
指向打印出来的是 undefined
。
-
在
render
里面用箭头函数React中自带的结构体中
this
指向正确,因为源码已经处理过了,因此利用自带结构体中的箭头函数class App extends React.Component { render() { return <button onClick = {() => this.handlerClick()}>点我</button> } handlerClick() { console.log(this); } }
缺点:会把大量的js处理逻辑放到JSX中,将来不容易维护
-
自定义方法,改成箭头函数
class App extends React.Component { render() { return <button onClick = {this.handlerClick}>点我</button> } handlerClick = () => { console.log(this); } }
注意:这个语法是试验性的语法,但是有babel的转义,所以没有任何问题
-
利用
bind
改变指向(了解)class App extends React.Component { render() { return <button onClick = {this.handlerClick.bind(this)}>点我</button> } handlerClick() { console.log(this); } }
案例:购物车
复用同一个事件。
class App extends React.Component {
state = {
count: 0,
};
render() {
return (
<div>
<p>当前数值为:{this.state.count}</p>
<hr />
<button onClick= {() => this.handlerClick(1)}>+1</button>
<span>{this.state.count}</span>
<button onClick= {() => this.handlerClick(-1)}>-1</button>
</div>
);
}
handlerClick = (num) => {
const {count} = this.state
this.setState({count: count + num})
}
}
表单
受控组件
基本概念
- 表单元素的值,由
state
控制 onChange
配合setState
修改state
的值
使用步骤
- 在state中添加一个状态,作为表单元素的value值(控制表单元素的值)
- 给表单元素添加change事件,设置state的值为表单元素的值(控制值的变化)
class App extends React.Component {
state = {
msg: 'hello react'
}
handleChange = (e) => {
this.setState({
msg: e.target.value
})
}
render() {
return (
<div>
<input type="text" value={this.state.msg} onChange={this.handleChange}/>
</div>
)
}
}
注意:
- 推荐使用
onChange
事件监听用户输入,不推荐使用onInput
。 react
中change
事件等同于html中的input
事件。react
中的blur
事件,等同于html中的change
事件。
常见的受控组件
- 文本框、文本域、下拉框(操作
value
属性) - 复选框(操作
checked
属性)
对象属性名称-插值技术
handleName = (e) => {
this.setState({
[e.target.name]: e.target.value
})
}
非受控组件
表单元素的value由DOM对象控制,不受state的控制
class Demo extends React.Compoment {
handelSave = () => {
const { username } = this.refs
console.log(username.value)
}
render() {
return (
<input ref="username" type="text" />
<button onClick={this.handelSave}>提交</button>
)
}
}
总结:
- 表单元素,它的值由DOM控制,不使用state控制,称为非受控组件。
- 表单元素,它的值由state控制,由setState改变,称为受控组件。
- **推荐-**使用受控组件,React推荐数据驱动视图。