类组件
React脚手架
create-react-app 脚手架的作用:帮你快速的构建一个工程化的开发react的环境
问题: JSX 转 JS 和 ES6 转 ES5 语法运行时编译太慢了
解决: 利用 Webpack 进行打包处理
问题: webpack打包环境搭建太麻烦, 且没有质量保证, 效率低
解决: 使用官方提供的`脚手架`工具
安装创建启动
* 全局安装脚手架
# npm
npm i create-react-app -g
# yarn
yarn add global create-react-app
* 使用create-react-app 命令 创建react项目
# npm
create-react-app 项目名 # 注意项目名不能叫 react
# yarn
yarn create react-app 项目名
# 项目名不能有中文
* 运行项目
# cd 项目目录
cd 项目目录
# npm
npm start
# yarn
yarn start
局部安装
1. 找一个目录【不能是中文】,打开终端
2. npm init -y 初始化目录
3. npm i create-react-app 局部安装
4. npx create-react-app 项目名,npx命令会默认寻找当前目录下的.bin下的文件
5. cd 项目名 npm start 进入项目目录并启动项目
目录结构分析
react-scaffold 项目根目录
|- node_modules npm包目录
|— public 静态资源目录
| |- favicon.ico 站点图标
| |- index.html 网站的入口html文件
| |- logo192.png 移动端图片
| |- logo512.png 移动端图片
| |- manifest.json 移动端图标的配置文件
| |- robots.txt 爬虫文件
|- src 程序员开发目录
| |- App.css 根组件的样式文件
| |- App.js 根组件App
| |- App.test.js 测试文件
| |- index.js js的入口文件
| |- index.css 入口文件的样式文件
| |- logo.svg 旋转的菊花图标
| |- reportWebVitals.js google的报告文件
| |- setupTests.js 测试启动文件
|- package-lock.json 包版本锁文件
|- package.json 包配置文件
项目目录精简
精简后的目录结构
src
|- index.js
|- App.js
public
|- index.html
|- favicon.ico
can't resolve xxx 不能够解析 xxx 文件:文件找不到
入口文件:src->index.js
import React from 'react';
import ReactDOM from 'react-dom/client';
// 导入App根组件
import App from './App';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<App />
);
根组件:src->App.js
function App() {
return (
<React.StrictMode>
{/* 开启严格模式 会在控制台输出两次console,所以关闭 */}
<div>App</div>
</React.StrictMode>
);
}
export default App;
首页:public->index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta
name="description"
content="Web site created using create-react-app"
/>
<title>React App</title>
</head>
<body>
<div id="root"></div>
</body>
</html>
快速创建组件
rcc: react class component 快速创建类组件
rfc: react function component 快速创建函数组件
组件
组件:组成整体的一个部件,简称组件
react中的组件:jsx(html结构 + js 逻辑)+ css + 静态资源(图片、视频、字体)
组件从概念上类似于 JavaScript 函数。
它接收参数(即 “props”),内部可以有自己的数据(即 “state”),并返回用于描述页面展示的 React 元素
组件分类
1. 类组件[16.4版本以前]:
2. 函数组件[18.xx]:
类组件
1. 组件名首字母必须大写.
2. 组件内部如果有多个标签,必须使用一个根标签包裹.只能有一个根标签
3. 类组件应该继承 React.Component 父类,从而可以使用父类中提供的方法或属性
4. 类组件中必须要声明一个render函数, reander返回组件代表组件界面的虚拟DOM元素
5. 会在组件标签渲染时调用, 产生实例对象(this->组件实例对象), 可以有状态
创建由类组件组成的页面
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>React功能验证</title>
<script src="./lib/react.development.js"></script>
<script src="./lib/react-dom.development.js"></script>
<script src="./lib/babel.min.js"></script>
</head>
<body>
<div id="root"></div>
</body>
<script type="text/babel">
const root = ReactDOM.createRoot(document.querySelector("#root"));
// 定义Header类组件
class Header extends React.Component {
// reader方法
render() {
// 类组件render方法,永远是当前类组件的实例对象调用的
console.log('header render this: ', this);
return (
<div className="header">我是头部</div>
)
}
}
// 定义Main类组件
class Main extends React.Component {
render() {
return (
<div className="main">我是内容</div>
)
}
}
// 定义Footer类组件
class Footer extends React.Component {
render() {
return (
<div className="footer">我是底部</div>
)
}
}
root.render((
<>
<Header />
<Main></Main>
<Footer></Footer>
</>
))
</script>
</html>
自定义render函数渲染react元素
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>React功能验证</title>
<script src="./lib/react.development.js"></script>
<script src="./lib/react-dom.development.js"></script>
<script src="./lib/babel.min.js"></script>
</head>
<body>
<div id="root"></div>
</body>
<script type="text/babel">
const root = ReactDOM.createRoot(document.querySelector("#root"))
// 结构 component 组件
const { Component } = React
// 创建类组件 Header
class Header extends Component{
// 定义方法
renderByClass() {
{/* 返回一个jsx语法的react元素 */}
return (
<div>我是Header</div>
)
}
}
// 自定义render函数
function renderByMy(reactComponent,root){
console.log('reactComponent: ', reactComponent);// react元素对象
console.log('root: ', root);
console.log('reactComponent.type: ', reactComponent.type);// class类 Header
// 实例化Header类
const instance = new reactComponent.type();
// 使用实例化对象调用 renderByClass 方法,得到react元素
const vdom = instance.renderByClass();
console.log('vdom: ', vdom);// 虚拟dom == react元素
// 通过vdom 创建 真实dom == div元素
const realDom = document.createElement(vdom.type);
// 给真实dom添加内容
realDom.innerHTML = vdom.props.children;
// 将真实dom添加到页面,完成渲染
root.appendChild(realDom);
}
// 获取真实DOM root根节点
const oRoot = document.querySelector('#root')
// 渲染 react 元素
renderByMy(<Header/>,oRoot)
</script>
</html>
基本介绍
类组件:本质就是一个类,使用class关键字定义。
语法:
1. 类名首字母必须大写,类名就是组件名。
2. 类组件必须继承 React.Component类
3. 类组件中必须有render方法
4. render方法中必须 [99.99%] 返回 react元素(jsx创建)
调用类组件:使用 jsx 语法进行调用
1. 单标签调用: <组件名/>
2. 对标签调用: <组件名></组件名>
调用过程:
1. jsx语法发现首字母大写的jsx调用标签,会将其当做组件处理
2. JS 查找是否定义了该组件,如果定义了,并且发现是类组件,那么render函数会帮咱们实例化该类,并用实例化出来的对象,调用类中render方法。
3. render方法执行后的返回值,会替换掉组件标签调用的位置
伪代码实现:JS 查找是否定义了该组件,如果定义了,并且发现是类组件,那么render函数会帮咱们实例化该类,并用实例化出来的对象,调用类中render方法,得到组件内的react元素(虚拟DOM),然后创建真实DOM元素,把虚拟DOM(react元素)放在真实DOM中。
注意:类组件中的render方法中的this,永远指向该类组件的实例对象
工程中的类组件
src->App.jsx
import HeaderClass from "./components/HeaderClass";
function App() {
return (
<div>
<h4>App</h4>
<HeaderClass />
</div>
)
}
export default App;
src->components->HeaderClass.jsx
import React, { Component } from 'react'
class HeaderClass extends Component {
render() {
return (
<div>HeaderClass</div>
)
}
}
export default HeaderClass
数据状态
类组件可以定义状态数据state
一旦更新state数据, 界面就会自动更新
* 类组件定义状态数据的方式,就是添加一个特殊名的属性 state
* 状态只能定义在state属性上,state是专有特殊的属性
定义状态的两种方式
在构造函数中定义
直接赋值定义
读取状态的方式
通过this.state读取
解构成变量后,读取
改变数据状态
数据定义到 state 中才能通过setState方法实现响应式,重新渲染。否则只能读不能改。
this.setState()方法修改
this.setState()是继承过来的方法
this.setState() 参数是一个对象:对象中是 要修改的属性和属性值
this.setState()的作用
1. 将状态数据的值改变
2. 触发render重新调用
this.setState({
count:this.state.count + 3
})
setState调用后:
1. 修改状态
2. 触发render函数重新调用
注意:父组件重新render,那么子组件也会无条件重新渲染
[如果是类组件-render方法重新调用、如果是函数组件-函数被重新调用]
渲染问题
注意:父组件重新render,那么子组件也会无条件重新渲染
定义状态的使用案例:在构造函数中定义状态
// 定义状态的方式一:
constructor(){
super();// 调用父类的构造函数
// 定义自己的属性 state, 就是类组件的状态数据
this.state = {
count:100,
msg:'atguigu'
}
}
定义状态的使用案例:直接使用state赋值定义
// 定义状态的方式二:
state = {
count:99,
msg:'atguigu123'
}
读取状态的使用案例:
render() {
console.log(this);// react元素对象(自己),render函数是类的实例对象调用的,永远指向当前实例对象
// 解构使用
let {count,msg} = this.state;
return (
<div>
<p>count : {this.state.count}-{count}</p>
<p>msg: {this.state.msg}-{msg}</p>
</div>
)
}
修改状态的使用案例:
import React, { Component } from 'react'
/**
* 状态数据研究三个方向:
* 1. 如何定义状态
* 2. 如何读取状态
* 3. 如何修改状态
*/
export default class App extends Component {
state = {
count:99,
msg:'atguigu123'
}
addCount(num){
/*
方式1 直接赋值改变状态数据
问题: 可以改变状态数据,但是不会触发组件重新渲染
*/
/* 这种方式不行 */
this.state.count += num;
console.log('count: ',this.state.count);
/*
方式2 通过 this.setState()方法修改
this.setState()是继承过来的方法
this.setState() 参数是一个对象:对象中是 要修改的属性和属性值
this.setState()的作用
1. 将状态数据的值改变
2. 触发render重新调用
*/
this.setState({
count: this.state.count + num
})
}
changeMsg(){
this.setState({
// msg: 'atguigu123123123123123212'
msg:this.state.msg + '!'
})
}
render() {
console.log('render run');
let {count,msg} = this.state;
return (
<div>
<p>count : {this.state.count}-{count}</p>
<p>msg: {this.state.msg}-{msg}</p>
<p><button onClick={()=>this.addCount(3)}>count++ </button></p>
<p><button onClick={()=>this.changeMsg()}>msg + !</button></p>
</div>
)
}
}
定义数据状态的原则
原则:
1. 如果数据只在一个组件身上使用,那么就当以在该组件自己身上
2. 如果数据在多个组件都需要使用,那么定义在他们共同的父级身上[也叫做状态提升]
JSX事件
原生DOM事件
通过标签属性绑定事件
1. 语法:on事件名="事件回调函数()" 例如:onclick='fn()'
2. 事件回调函数的调用者是 window,所以this指向window
3. 通过传递实参event(window.event),获取事件对象
4. 阻止默认行为:e.preventDefault();
5. 可以同时传递普通参数和事件对象
6. 如果想获取指向按钮的this,需要通过实参传递
原生 oninput 事件 和 onchange事件的区别
1. 触发时机不同:
- oninput: 键盘有输入就立刻触发
- onchange:内容有变化,并且失去焦点时触发
2. 事件对象不同:
- oninput:InputEvent
- onchange:Event
原生DOM事件使用案例
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>React功能验证</title>
<script src="./lib/react.development.js"></script>
<script src="./lib/react-dom.development.js"></script>
<script src="./lib/babel.min.js"></script>
</head>
<body>
<div id="root"></div>
<p><button onclick="fn(event);">按钮</button></p>
<p><a href="http://baidu.com" onclick="fn(event)">百度</a></p>
<p><button onclick="f1(1,2)">自定义参数和事件对象</button></p>
<p><button onclick="f2(1,event)">自定义参数和事件对象</button></p>
<p><button onclick="f3(this)">实参this,指向当前按钮</button></p>
<input type="text" name="" id="" oninput="f4(event)">
<input type="text" name="" id="" onchange="f5(event)">
</body>
<script>
function fn(e) {
console.log('this: ', this); // window
console.log('e: ', e); // 事件对象 PointerEvent
e.preventDefault(); // 阻止默认行为跳转
}
function f1(a, b) {
console.log('a: ', a); // 1
console.log('b: ', b); // 2
}
function f2(a, b) {
console.log('a: ', a); // 1
console.log('b: ', b); // 事件对象 PointerEvent
}
// 这里的_this 也可以写成that
function f3(_this) {
console.log('_this: ', _this);// 指向当前按钮:当前点击的按钮对象button
console.log(this);// window 函数f3的调用者是window
}
function f4(e) {
//oninput 触发时机,有输入就触发
console.log('oninput value: ', e.target.value)
console.log('oninput e: ', e);//事件对象 InputEvent
}
function f5(e) {
// onchange 内容有变化,并且失去交点触发
console.log('onchange value: ', e.target.value)
console.log('onchange e: ', e);//事件对象 Event
}
</script>
</html>
JSX事件
绑定事件的语法:【通过标签属性的方式绑定】,函数名用插值表达式包裹
<button onClick={函数名}>click</button>
*事件回调函数研究:
*this指向问题:react的事件回调函数的调用者是window,所以this指向window(同原生DOM一致),因为react使用的是严格模式,所以,this指向undefined
*事件对象:
1. react的事件对象默认可以通过第一个形参进行接收
2. react的事件对象是一个经过react处理后的事件对象,原生的事件对象的常用属性都有,并且做了兼容性处理
3. 如果想获取原生的事件对象,可以通过nativeEvent 属性获取
4. 通过 e.preventDefault() 阻止默认行为
*事件回调函数传递参数
1. 包裹箭头函数:
<button onClick={()=>click(1,2)}>参数</button>
2. 即传递参数又传递事件对象 click是自己写的回调函数:
<button onClick={(e)=>click(e,1,2)}>参数事件对象同时传递</button>
*jsx中的 onChange事件实际是原生的 oninput事件
1. 触发时机是键盘输入就触发
2. 事件对象是 InputEvent
* 在标签属性上通过 on原生事件名=事件回调函数 进行绑定
1. 事件名首字母大写,使用驼峰命名法则:onKeyUp,onKeyDown
2. 事件回调函数填的是函数名[函数的定义、函数的引用地址],而不是函数的调用(原生DOM中写的是函数调用)
* -----------
* this指向问题:undefined 说明 react中的事件回调的调用者也是window,严格模式所以是undefined
* React中事件对象e:
1. 一个经过react包装后的事件对象,原生的事件对象常用属性,基本都有。这个事件对象更好用,已经做过兼容性处理了
2. 如果想获取原生的事件对象:使用e.nativeEvent获取
3. 事件的回调函数是window帮我们调用的,会默认的将事件对象作为第一个实参传递过来
* 事件回调函数参数的传递
* 包裹一个箭头函数,在箭头函数内部调用函数click并传递参数
<button onClick={(e)=>click(e,1,2)}>参数事件对象同时传递</button>
* react中的 onChange事件,实际是原生的 oninput事件,有输入就触发
事件回调函数的绑定及调用使用案例
<script type="text/babel">
const root = ReactDOM.createRoot(document.querySelector("#root"));
function click1() {
console.log('click1');
console.log('this: ', this);//undefined
}
function click2(e) {
console.log(e); // SyntheticBaseEvent 事件对象
e.preventDefault(); // 阻止默认行为
}
function click3(a, b) {
console.log('click3');
console.log('a: ', a); // undefined
console.log('b: ', b); // undefined
}
function outFn() {
click3(1, 2);
}
function click4(e, a, b) {
console.log('e: ', e);
console.log('a: ', a);
console.log('b: ', b);
e.preventDefault();
}
function change(e) {
console.log('change e: ', e); // e: SyntheticBaseEvent 事件对象
console.log('value: ', e.target.value); // 1 12 123
}
root.render((
<div>
{/* 事件触发执行回调函数click1*/}
<p><button onClick={click1}>click1</button></p>
{/* 事件触发执行回调函数click2*/}
<p><a href="http://baidu.com" onClick={click2}>百度</a></p>
<h3>事件回调函数传参</h3>
{/* 不能直接调用函数,这样就相当于插值表达式使用函数的返回值,return 空,相当于undefined,a: undefined,b: undefined ,点击无反应,错误的示范*/}
<p><button onClick={click3()}>click3</button></p>
{/* 通过函数传递参数,a: 1,b: 2 ,正确的示范*/}
<p><button onClick={outFn}>传递参数</button></p>
{/* 通过包裹匿名函数传递参数,a: 1,b: 2 ,正确的示范*/}
<p><button onClick={function () { click3(2, 3) }}>包裹匿名函数传递参数</button></p>
{/* 通过箭头函数传递参数,a: 1,b: 2 ,正确的示范*/}
<p><button onClick={() => click3(2, 3)}>包裹箭头函数传参</button></p>
<h3>即传递参数,又同时传递事件对象</h3>
{/* 通过箭头函数传递参数:e: SyntheticBaseEvent 事件对象 a: 1,b: SyntheticBaseEvent*/}
<p><button onClick={(e) => { click3(1, e) }}>即传递参数,又同时传递事件对象</button></p>
{/* 通过箭头函数传递参数:e: SyntheticBaseEvent 事件对象 a: 100,b: 222 */}
<p><a href="http://baidu.com" onClick={(e) => click4(e, 100, 222)}>百度</a></p>
{/* 通过函数传递参数 react中的 onChange事件,实际是原生的 oninput事件 有输入就触发*/}
<input type="text" onChange={change} name="" id="" />
</div>
))
</script>
类组件事件this指向
类组件事件回调this指向windows。
因为react的事件回调函数的调用者是window,
所以定义成普通函数的事件回调中的this在严格模式下值是undefined。
我们希望事件回调中的this可以指向当前组件的实例,实现方式有如下几种
1. 通过bind: 使用bind方法绑定this
2. 包裹箭头函数: 利用箭头函数中没有this,this取决于上层作用域:render中的this永远指向实例
3. bind结合constructor : 在constructor中使用bind方法绑定this
4. 直接定义成箭头函数 :利用箭头函数中没有this,this取决于上层作用域(不用,无法传参,且占用内存)
无法传参,且占用内存:每一个实例上都有一个相同的方法,占内存
-------------------
解决办法1 - 包裹箭头函数
原因: render中的this是组件对象, 处理函数是我们通过组件对象来调用的
解决办法2 - bind绑定this
原因: 构造器中的this是组件对象, 将处理函数通过bind绑定为了组件对象
解决办法3 - 箭头函数
原理: 利用bind给事件回调绑定this为组件对象(render中的this)
改变事件回调函数中this的指向
import React, { Component } from 'react'
export default class App extends Component {
state = {
count:99,
msg:'atguigu123'
}
click1(){
console.log('click1 this: ', this); // 事件的回调是window调用的,所以是undefined
}
click2(){
// bind作用: 改变函数的this指向,返回一个新的函数
console.log('click2 this: ', this);
}
click3(){
// 包裹箭头函数,使用外部作用域 render中的this => 组件实例
console.log('click3 this: ', this);
}
// 不推荐:原因:无法传参,且占用内存:每一个实例上都有一个相同的方法,占内存
click4 = ()=>{
// 使用的是constructor中的this => 组件实例
console.log('click4 this: ', this);
}
// 结合构造函数设置click5中this
click5(){
console.log('click5 this: ', this);
}
constructor(){
super();
// bind 配合constructor 实现改变click5this指向当前实例
this.click5 = this.click5.bind(this);
}
/**
* 原则:
* 1. 是否可以传参 bind(this,'参数1','参数2') 包裹箭头函数
* 2. 是否占用内存空间 bind(this,参数1,参数2) 包裹箭头函数
*
*/
render() {
console.log(this);// render函数是类的实例对象调用的,永远指向当前实例对象
// 解构使用
let {count,msg} = this.state;
return (
<div>
<p>count : {this.state.count}-{count}</p>
<p>msg: {this.state.msg}-{msg}</p>
<p><button onClick={this.click1}>count++ 有问题的</button></p>
<p><button onClick={this.click2.bind(this)}>通过bind修改 count++ </button></p>
<p><button onClick={()=>this.click3()}>包裹箭头函数</button></p>
<p><button onClick={this.click4}>直接定义成箭头函数</button></p>
<p><button onClick={this.click5}>bind结合构造函数</button></p>
</div>
)
}
}
bind传递参数案例
import React from 'react';
class App extends React.Component {
handleClick(param, event) {
console.log('传递的参数:', param);
console.log('事件对象:', event);
}
render() {
return (
<button onClick={this.handleClick.bind(this, '自定义参数')}>
点击我
</button>
);
}
}
export default App;
props 外部数据
组件间通信的借助props传递
父组件向子组件传递数据
- 父组件如何传递数据给子组件?
1. 通过子组件调用标签属性的方式传递
- 子组件如何接收父组件传递的数据?
1. 类子组件通过 this.props属性接收
2. 函数子组件通过函数的形参接收【一般会直接在参数位置解构】
子组件向父组件传递数据,也是借助props
1. 在父组件定义一个方法,方法设置一个或多个形参
2. 将该方法改变this指向,让this指向当前组件的实例对象
3. 将该方法通过标签数据性的方式传递给子组件
4. 在子组件中通过 props接收
1. 类组件: this.props.方法名
2. 函数组件: props.方法名
5. 在子组件中调用该方法,并将要传递的数据以实参的方式传递
注意:
1. props外部数据是只读的,在子组件中不可以直接修改
2. props的children属性,可以接收到组件调用对标签中的子元素
父向子传值
子组件通过this.props接收数据
父组件:src->App.js
import React, { Component } from 'react'
import ClassCom from './components/Child';
import FunCom from './components/FunCom';
/**
* 状态数据研究三个方向:
* 1. 如何定义状态
* 2. 如何读取状态
* 3. 如何修改状态
*/
export default class App extends Component {
state = {
count:99,
msg:'atguigu123'
}
addCount(num){
this.setState({
count: this.state.count + num
})
}
changeMsg(){
this.setState({
// msg: 'atguigu123123123123123212'
msg:this.state.msg + '!'
})
}
render() {
console.log('render run');
let {count,msg} = this.state;
return (
<div>
<p>count : {this.state.count}-{count}</p>
<p>msg: {this.state.msg}-{msg}</p>
<p><button onClick={()=>this.addCount(3)}>count++ </button></p>
<p><button onClick={()=>this.changeMsg()}>msg + !</button></p>
<hr/>
{/* 父组件通过子组件标签属性传递数据给子组件 */}
{/* <ClassCom num={count} xiaoxi={msg} school='尚硅谷'/> */}
{/* 前边的是传给子组件的属性,后边的是传的值 */}
<ClassCom count={count} msg={msg} school='尚硅谷'/>
<FunCom/>
</div>
)
}
}
子组件:src->Child.js:通过固定属性 this.props接收外部数据
import React, { Component } from 'react'
export default class ClassCom extends Component {
render() {
// 子组件通过特殊属性 this.props进行接收
console.log('classCom render');
console.log('this.props: ', this.props); // this 指向当前组件实例
// 解构后使用
let {count, msg, school} = this.props
return (
<div>
<h3>ClassCom</h3>
<p>props count: {this.props.count}-{count}</p>
<p>props msg: {this.props.msg}-{msg}</p>
<p>props school: {this.props.school}-{school}</p>
</div>
)
}
}
子向父传值
1. 父组件定义方法
2. 通过 子组件标签属性将方法传递给子组件 [注意要改变该方法的this指向,改变方法中this的指向为父组件实例]
3. 子组件通过props接收
4. 子组件调用方法并将数据以实参的形式传递
父组件:src->App.js
import React, { Component } from 'react'
import ClassCom from './components/Child';
import FunCom from './components/FunCom';
/**
* 状态数据研究三个方向:
* 1. 如何定义状态
* 2. 如何读取状态
* 3. 如何修改状态
*/
export default class App extends Component {
state = {
count:99,
msg:'atguigu123'
}
addCount(num){
this.setState({
count: this.state.count + num
})
}
changeMsg(){
this.setState({
// msg: 'atguigu123123123123123212'
msg:this.state.msg + '!'
})
}
// 1. 定义一个方法
decCount(num){
this.setState({
count:this.state.count - num
})
}
render() {
console.log('render run');
let {count,msg} = this.state;
return (
<div>
<p>count : {this.state.count}-{count}</p>
<p>msg: {this.state.msg}-{msg}</p>
<p><button onClick={()=>this.addCount(3)}>count++ </button></p>
<p><button onClick={()=>this.changeMsg()}>msg + !</button></p>
<hr/>
{/* 将decCount this指向 为当前实例对象后传递给子组件 */}
<ClassCom count={count} msg={msg} school='尚硅谷' decCount={this.decCount.bind(this)}/>
{/* 函数子组件也是通过属性传递数据 */}
<FunCom count={count} msg={msg} school='尚硅谷' decCount={this.decCount.bind(this)}/>
</div>
)
}
}
子组件:src->Child.js
render() {
// 子组件通过特殊属性 this.props进行接收
console.log('classCom render');
console.log('this.props: ', this.props);
// 解构后使用
let {count, msg, school,decCount} = this.props
return (
<div>
<h3>ClassCom</h3>
<p>props count: {this.props.count}-{count}</p>
<p>props msg: {this.props.msg}-{msg}</p>
<p>props school: {this.props.school}-{school}</p>
<p><button onClick={()=>{
// props数据是只读的,不可修改
this.props.count = 10000;
}}>修改props 中的 count</button></p>
<p><button onClick={()=>{
decCount(5);
}}>子传父</button></p>
</div>
)
}
props.children属性
作用:当组件进行对标签调用的时候,可以获取对标签中的子元素
当我们使用 对标签 中的子元素的时候,需要使用对标签
props.children使用案例
父组件:src->App.jsx
import React, { Component } from 'react'
import Button from './components/Button'
export default class App extends Component {
render() {
return (
<div>
<Button>保存</Button>
<Button>取消</Button>
<Button>提交</Button>
</div>
)
}
}
子组件:src->Child.jsx
import React, { Component } from 'react'
export default class Button extends Component {
render() {
let {children} = this.props; // 获取对标签调用的子元素
return (
<button>{children}</button>
)
}
}
限定props类型
使用prop-types 包,对传入的外部数据进行类型、必填、默认值的限定
// 默认已经安装好了
import PropTypes from 'prop-types'
类组件是通过定义静态属性的方式实现
static propTypes = {
name:PropTypes.string.isRequired, // name是字符串,且必须传
age:PropTypes.number // age 是数字,可以不传
}
static defaultProps = {
age:100 // 限定默认值
}
使用案例
父组件:src->App.jsx
import React, { Component } from 'react'
import Child from './components/Class/Child'
export default class App extends Component {
render() {
return (
<div>
<h3>App</h3>
{/* {99} : 99是number类型,默认不带{}直接写99是string类型 */}
<Child age={99} name='atguigu' />
</div>
)
}
}
子组件:src->components->Child.jsx
import React, { Component } from 'react'
// 1. 引入属性检查的包
import PropTypes from 'prop-types'
export default class Child extends Component {
// 定义静态属性
static propTypes = {
name: PropTypes.string.isRequired, // name是字符串,且必须传
age: PropTypes.number // age 是数字,可以不传
}
// 定义静态方法
static defaultProps = {
age: 100 // 限定默认值
}
render() {
let { name, age } = this.props;
return (
<div>
<h3>Child</h3>
<p>props name: {name}</p>
<p>props age: {age}</p>
</div>
)
}
}
函数子组件:src->components->Fun->Child.jsx
import React from 'react'
import PropTypes from 'prop-types'
function Child({ name, age }) {
return (
<div>
<h4>函数组件</h4>
<p>props name: {name}</p>
<p>props age: {age}</p>
</div>
)
}
// 通过给函数对象设置propTypes属性的方式设置类型
Child.propTypes = {
name: PropTypes.string.isRequired,
age: PropTypes.number
}
// 通过给函数对象设置defaultProps属性的方式设置默认值
Child.defaultProps = {
age: 10000
}
export default Child
React18警告-defaultProps
Table: Support for defaultProps will be removed from function components in a future major release. Use JavaScript default parameters instead.
React 在未来大版本会移除函数组件的 defaultProps 属性支持,改用 js 函数方法的 default 值替换。
点击跳转确实看到了 antd 的 Table 组件使用了 detaultProps 属性。
这是比较老的写法了,直接用 TS 可以替代这种。
react 未来的大版本应该说的是 React-19 了,在官网找到了说明:
目前 antd 并没有升级版本来解决这个警告,看着可以通过 eslint 关闭这个警告
1. 安装依赖 npm install eslint-plugin-react --save-dev
2. 修改 eslint 配置
module.exports = {
plugins: ['react'],
extends: ['plugin:react/recommended'],
rules: {
'react/require-default-props': 'off'
}
};
(plugin:react/recommended是eslint-plugin-react插件提供的一个推荐的规则配置)
我试了下,继承 react/recommended 和我们已有的集成 react-app 差比比较大,会出现更多个告警。
生命周期
挂载阶段
流程: constructor ==> render ==> componentDidMount
触发: ReactDOM.render(): 渲染组件元素
componentDidMount的执行时机,组件挂载完成后执行
常用于:
1. 开启定时器
2. 发送ajax请求
3. 订阅消息
4. 添加自定义事件
componentDidMount钩子使用案例
import React, { Component } from 'react'
export default class App extends Component {
constructor() {
super();
console.log('App constructor');
}
render() {
console.log('App render');
return (
<div>App</div>
)
}
/**
* component : 组件
* Did: do 过去式 完成
* Mount: 挂载
* componentDidMount 组件完成挂载之后执行, jsx已经渲染成真实dom出现在页面中了
*
* 作用:
* 1. 开启定时器
* 2. 发送ajax请求 axios
* 3. 订阅消息
* 4. 添加自定义事件
* 注意:constructor 和 componentDidMount 只执行一次
*/
componentDidMount() {
console.log('App componentDidMount');
}
}
电子时钟案例练习
import React, { Component } from 'react'
// 1. 安装moment npm i moment
// 2. 导入moment
import moment from 'moment'
export default class App extends Component {
state = {
nowDate: moment().format('YYYY-MM-DD HH:mm:ss')
}
constructor() {
super();
console.log('App constructor');
}
render() {
console.log('App render');
let {nowDate} = this.state;
return (
<div>
当前时间是: {nowDate}
</div>
)
}
componentDidMount() {
// 生命周期函数都是react在特定时间节点调用的,所有生命周期函数中的this都指向当前组件的实例对象
setInterval(()=>{
this.setState({
nowDate:moment().format('YYYY-MM-DD HH:mm:ss')
})
},1000)
}
}
更新阶段
流程: render ==> componentDidUpdate
触发: 组件更新完成后更新
触发: 1. setState() , 2. forceUpdate(), 3. 组件接收到新的props。
只有以上三种情况会触发组件的 render重新调用,进而触发 componentDidUpdate的执行。
只要执行setState() 都会更新,无论setState的值有没有发生变化。
componentDidUpdate常用于:
1. 发送ajax请求
2. 可以更新本地存储的数据 localStorage sessionStorage
componentDidUpdate使用案例
src->App.jsx
import React, { Component } from 'react'
// 1. 安装moment npm i moment
// 2. 导入moment
import moment from 'moment'
import Son from './components/Son';
export default class App extends Component {
state = {
nowDate: moment().format('YYYY-MM-DD HH:mm:ss')
}
constructor() {
super();
console.log('App constructor');
}
render() {
console.log('App render');
let { nowDate } = this.state;
return (
<>
<div>
当前时间是: {nowDate}
</div>
{/* 只要执行setState就会触发render函数的调用,进而触发 componentDidUpdate生命周期的执行 */}
<p>
<button type="button" onClick={()=>{
this.setState({
nowDate:moment().format('YYYY-MM-DD HH:mm:ss')
})
}}>setState</button>
</p>
<p><button onClick={()=>{
this.setState({
nowDate:'这一瞬间,就是永恒!'
})
}}>setState固定值</button></p>
{/* forceUpdate: force: 强制 update更新 */}
<p><button onClick={()=>{
this.state.nowDate = '直接赋值修改';//不会触发render函数执行
this.forceUpdate();// 强制更新 ,会触发 render函数执行,进而触发 componentDidUpdate的执行
}}>强制更新</button></p>
{/* 使用this.props更新触发更新 */}
<hr/>
<Son nowDate={this.state.nowDate}/>
</>
)
}
componentDidMount() {
console.log('App componentDidMount');
}
/**
* 组件更新完成后触发
* update: 更新
* componentDidUpdate
*
* 作用:
* 1. ajax请求
* 2. 可以修改本地存储 localStorage sessionStorage
*
*/
componentDidUpdate() {
console.log('componentDidUpdate')
}
}
src->components->Son.jsx
import React, { Component } from 'react'
export default class Son extends Component {
constructor(){
super();
console.log('Son constructor');
}
componentDidMount(){
console.log('Son componentDidMount');
}
componentDidUpdate(){
console.log('Son componentDidUpdate');
}
render() {
console.log('Son render');
let {nowDate} = this.props;
return (
<div>
<h3>Son</h3>
<p>props- nowDate: {nowDate}</p>
</div>
)
}
}
挂载阶段 父子组件生命周期执行顺序
App constructor
App render
Son constructor
Son render
Son componentDidMount
App componentDidMount
更新阶段 父子组件生命周期执行顺序
App render
Son render
Son componentDidUpdate
App componentDidUpdate
卸载阶段 父子组件生命周期执行顺序
App render
Son componentWillUnmount
App componentDidUpdate
卸载阶段
流程: componentWillUnmount
触发: 组件卸载前执行
componentWillUnmount常用于:
1. 取消定时器
2. 取消订阅消息
3. 解绑自定义事件
componentWillUnmount使用案例
父组件:src->App.jsx
import React, { Component } from 'react'
import Son from './components/Son'
export default class App extends Component {
state = {
count:1
}
render() {
return (
<div>
<h3>App</h3>
<p>state count: {this.state.count}</p>
<p><button onClick={()=>{
this.setState({
count: this.state.count + 1
})
}}>count++</button></p>
<hr />
{/*条件渲染,控制Son组件的挂载和卸载*/}
{this.state.count % 2 === 0 && <Son/>}
</div>
)
}
}
子组件:src->Child.jsx
import React, { Component } from 'react'
export default class Son extends Component {
constructor(){
super();
console.log('Son constructor');
}
componentDidMount(){
console.log('Son ComponentDidMount')
}
/**
* component组件
* will 即将
* unmount: 卸载
* 组件即将卸载的之前执行:【临终遗言】
* 作用:
* 1. 关闭定时器
* 2. 取消订阅
* 3. 解绑自定义事件
*/
componentWillUnmount(){
console.log('Son componentWillUnmount')
}
render() {
return (
<div>Son</div>
)
}
}
组件取消定时器案例
src->App.jsx
import React, { Component } from 'react'
import Son from './components/Son'
export default class App extends Component {
state = {
count: 1
}
render() {
return (
<div>
<h3>App</h3>
<p>state count: {this.state.count}</p>
<p><button onClick={() => {
this.setState({
count: this.state.count + 1
})
}}>count++</button></p>
<hr />
{this.state.count % 2 === 0 && <Son />}
</div>
)
}
}
src->Child.jsx
import React, { Component } from 'react'
import moment from 'moment'
export default class Son extends Component {
state = {
nowDate: moment().format('YYYY-MM-DD HH:mm:ss')
}
constructor() {
super();
console.log('Son constructor');
}
componentDidMount() {
// 开启定时器
console.log('Son ComponentDidMount')
// 创建一个自定义属性,将定时器放在自定义属性上
this.timer = setInterval(() => {
console.log('interval');
this.setState({
nowDate: moment().format('YYYY-MM-DD HH:mm:ss')
})
}, 1000)
}
componentWillUnmount() {
console.log('Son componentWillUnmount')
clearInterval(this.timer);
}
render() {
let { nowDate } = this.state;
return (
<div>
当前时间: {nowDate}
</div>
)
}
}
生命周期钩子
constructor
- constructor: 造
只执行一次: 创建组件对象挂载第一个调用
用于初始化state属性或其它的实例属性或方法(可以简写到类体中)
render
- render: 生
执行多次: 挂载一次 + 每次state/props更新都会调用
用于返回要初始显示或更新显示的虚拟DOM界面
componentDidMount
- componentDidMount: 打
执行一次: 在第一次调用render且组件界面已显示之后调用
用于初始执行一个异步操作: 发ajax请求/启动定时器等
应用:
1. 启动定时器
2. 订阅消息
3. 发送ajax请求
componentDidUpdate
- componentDidUpdate:
执行多次: 组件界面更新(真实DOM更新)之后调用
用于数据变化后, 就会要自动做一些相关的工作(比如: 存储数据/发请求)
用得少 => 这次我们先简单了解, 后面需要时再深入说
componentWillUnmount
- componentWillUnmount:
执行一次: 在组件卸载前调用
用于做一些收尾工作, 如: 清除定时器、取消订阅
ref对象
有3种用法:
1 在react项目中,获取真实dom元素对象
2 可以实现componentDidUpdate作用(详细见函数组件生命周期)
3 绑定组件
获取真实dom对象
使用步骤:
1. 创建一个ref对象: const ref1 = React.createRef()
2. 给jsx元素,绑定ref属性 <input ref={ref对象}/>
3. 通过ref对象的current属性获取真实dom==> ref对象.current
使用案例
import React, { Component,createRef } from 'react'
export default class App extends Component {
// 1. 创建ref对象,并让其是类组件的私有属性
inputRef = createRef();
render() {
console.log('inputRef: ',this.inputRef);// {current: null}
return (
<div>
{/* 2. ref绑定要获取真实dom的jsx */}
<input type="text" ref={this.inputRef}/>
<p><button onClick={()=>{
// 获取input真实dom
console.log('this.inputRef: ',this.inputRef);
console.log('input value: ', this.inputRef.current.value)
}}>获取 用户输入的 input 的内容</button></p>
</div>
)
}
}
绑定组件
给类组件绑定ref属性
1. 创建一个ref对象: const classRef = React.createRef()
2. 给类组件,绑定ref属性 <ClassText ref={classRef}/>
3. 通过ref对象的current属性获取 (类组件的实例对象ClassText) ==> classRef.current
给函数组件绑定ref属性:详情见函数组件章节
受控组件(表单元素)
相当于Vue中的双向绑定(数据状态和DOM中的相同)
相对于表单元素而言的
什么是受控组件?
答:当给表单元素的value属性的值赋值为状态数据的时候,那么表单元素的值就收到了状态数据的控制,称为受控组件。
一旦受控,表单元素变为只读的,用户输入不可修改。如果想让用户可以输入,需要添加onChange事件,在事件回调中,获取用户最新的输入,用来给状态赋值。
type='text' 输入框:通过value属性进行受控
<input type="text" name="" value={username} onChange={this.changeUsername.bind(this)}/>
type='radio' 单选 : 通过 checked 进行受控
<input type="radio" name="sex" value="1" checked={sex === '1'} onChange={this.change.bind(this)}/>
type='checkbox' 多选 : 通过 checked 进行受控
<input type="checkbox" name="sex" value="1" checked={sex === '1'} onChange={this.change.bind(this)}/>
受控组件使用案例
import React, { Component } from 'react'
/**
* 受控组件:相对于表单元素来说的
* 什么是受控组件:表单元素的值,受到组件状态数据的控制
*
*/
export default class FormControl extends Component {
state = {
username: 'atguigu',
password: '123123',
sex: '0'
}
save(e) {
// 阻止默认行为
e.preventDefault();
console.log('save')
console.log('username: ', this.state.username);
console.log('password: ', this.state.password);
console.log('sex: ', this.state.sex);
}
// changeUsername(e){
// console.log('e.target.name: ', e.target.name);// 获取表单元素的name属性值
// this.setState({
// [e.target.name]: e.target.value
// })
// }
// changePassword(e){
// console.log('e.target.name: ', e.target.name);
// this.setState({
// [e.target.name]: e.target.value
// })
// }
// 使用change方法优化替代上边的方法
// 使用 e.target.name 获取元素对象的name属性值
change(e) {
this.setState({
[e.target.name]: e.target.value
})
}
render() {
let { username, password,sex } = this.state;
return (
<>
<form action="" onSubmit={this.save.bind(this)}>
{/*
当把状态数据,赋值给表单的value属性,该表单元素受控
表单受控会有一下后果:
1. 表单的内容变成只读的了,不能修改了
2. 如果受控还想能够让用户输入新内容,需要给受控的表单添加 onChange 事件,在onChange的事件处理函数中,获取用户最新的输入,用用户最新的输入值,给数据状态赋值,即可解除只读属性
3. 组件受控,并通过onChange绑定状态,实现了状态数据和表单值的双向绑定
*/}
<p>姓名: <input type="text" name="username" value={username} onChange={this.change.bind(this)} /></p>
<p>密码: <input type="text" name="password" value={password} onChange={this.change.bind(this)} /></p>
{/* 针对与 radio ,受控不是受value属性值的控制,而是受 checked属性值的控制 */}
<p>
性别: 男 <input type="radio" name="sex" value="1" checked={sex === '1'} onChange={this.change.bind(this)}/>
女 <input type="radio" name="sex" value="0" checked={sex==='0'} onChange={this.change.bind(this)}/>
</p>
<hr />
{/*
以下都是可以提交的按钮
当点击保存按钮的时候,会将表单数据,提交到 action执行的地址,如果没有action属性,或者action属性为空,那么默认提交到当前地址,会刷新页面
提交按钮,会触发 form表单的 onSubmit事件,如果想阻止默认行为,通过 onSubmit的事件处理函数中的事件对象,e.preventDefault()进行阻止
*/}
<p><button type='submit'>保存</button></p>
{/* 在form标签中button按钮默认有提交属性 */}
<p><button >保存2</button></p>
<p><input type='submit' value='保存3' /></p>
{/* 以下是不能提交的 */}
<p><button type='button'>不能提交</button></p>
</form>
</>
)
}
}
非受控组件(表单元素)
什么是非受控组件:
表单元素的value或checked值,不受到状态数据的控制,充当设置默认值的作用
将状态数据渲染到表单中,使用 defaultValue 、defaultChecked
要想获取用户最新的输入,需要通过ref对象获取
非受控组件的使用案例
import React, { Component,createRef } from 'react'
export default class FormOutControl extends Component {
state = {
username:'尚硅谷',
password:'123123',
sex:'1'
}
// 1. 创建ref对象
usernameRef = createRef()
passwordRef = createRef()
sexRef = [createRef(),createRef()]
save(e){
e.preventDefault();
console.log('username: ', this.usernameRef.current.value);
console.log('password: ', this.passwordRef.current.value);
console.log(this.sexRef);// [{current:{checked:false,value:'1'}}, {current:{checked:true, value:'0'}}]
console.log('sex: ', this.sexRef.find(item=>item.current.checked).current.value);
}
render() {
let {username, password, sex} = this.state;
return (
<>
<form action="" onSubmit={this.save.bind(this)}>
<p>姓名: <input type="text" ref={this.usernameRef} name="username" defaultValue={username} /></p>
<p>密码: <input type="text" ref={this.passwordRef} name="password" defaultValue={password} /></p>
<p>
性别: 男 <input type="radio" ref={this.sexRef[0]} name="sex" value="1" defaultChecked={sex === '1'} />
女 <input type="radio" name="sex" ref={this.sexRef[1]} value="0" defaultChecked={sex === '0'} />
</p>
<p><button type='submit'>保存</button></p>
</form>
</>
)
}
}
PureComponent
优化类组件中数据无变化时仍重复渲染问题
背景:为了解决类组件中渲染问题:父组件重新render,那么子组件也会无条件重新渲染(不管子组件数据是否有无变化都重新渲染)
使用语法
PureComponent:当自身状态数据[state]和外部数据[props]没有变化的时候,组件不重新渲染
// 导入
import {PureComponent} from 'react'
// 使用,继承PureComponent而不是默认的Component
export default class App extends PureComponent {
...
}
使用案例
父组件:src->App.jsx
import React, { Component,PureComponent } from 'react'
import Hanser from './components/Hanser';
export default class App extends PureComponent {
state = {
count: 88
}
render() {
console.log('App render');
let { count } = this.state;
return (
<div>
<h3>App</h3>
<p>App state count: {count}</p>
<p><button onClick={() => {
this.setState({
count: count + 1
})
}}>count ++</button></p>
<p><button onClick={() => {
this.setState({
count: 99
})
}}>count == 99</button></p>
<hr />
<Hanser count={count} />
</div>
)
}
}
子组件:src->components->Hanser.jsx
import React, { Component, PureComponent } from 'react'
export default class Son extends PureComponent {
state = {
money: 2000
}
render() {
console.log('Son render');
let { money } = this.state;
let { count } = this.props;
return (
<div>
<h3>Son</h3>
<p>Son state money: {money}</p>
<p>Son props count: {count}</p>
<p><button onClick={() => {
this.setState({
money: money + 100
})
}}>money++</button></p>
<p><button onClick={() => {
this.setState({
money: 10000
})
}}>money == 10000</button></p>
</div>
)
}
}