react基础
1.react简介
1.1 React 介绍
1.1.1 React是什么
- React 是一个用于构建用户界面(HTML 页面)的 JavaScript 库
- 如果从mvc的角度来看,React仅仅是视图层(V)的解决方案。也就是只负责视图的渲染,并非提供了完整了M和C的功能
- 参考网址:react官网(reactjs.org/) ,react中文网(zh-hans.reactjs.org/)
1.1.2 React特点
- 组件化:组件用于表示页面中的部分内容,多个组件组合复用即可实现完整页面功能,是react中的核心内容之一
- 声明式UI:可直接在js文件中描述html结构
- 应用范围广:可开发web应用 / 移动端应用 / VR应用等
1.1.3 React 脚手架创建项目
-
原始方式(不推荐)
//全局安装脚手架模块包 ,如已有则不用重复安装模块包 npm i -g create-react-app //通过命令创建react项目 项目名不能含中文 create-react-app xxx -
新方式(不用担心脚手架模块包版本问题,自动使用最新版)
//npx是node提供的命令,命令执行流程:自动下载脚手架模块包-->创建相应项目-->自动卸载脚手架模块包 npx create-react-app xxx
1.1.4 React的基本使用
// 1.引入依赖包
import React from 'react';
import ReactDOM from 'react-dom';
// 2.调用方法创建元素React.createElement('标签名',标签的属性对象,标签内容)
const title = React.createElement('h2', { id: 'test' }, 'helloReact');
const contest = React.createElement('a',{ href: 'http://www.baidu.com' },'跳转到百度');
// 3.调用方法渲染虚拟dom, ReactDOM.render(创建的虚拟dom ,要挂载的 真实存在的dom)
// 注意:ReactDOM.render方法不能调用多次,会覆盖
ReactDOM.render(title, document.querySelector('#root'));
ReactDOM.render(contest, document.querySelector('#root')); //最终页面上会只显示这个
2. JSX语法糖
2.1 JSX简介
-
jsx本质上就是React.createElement的语法糖,不在使用React.createElement的繁琐写法,而是使用更直观的声明式语法,与HTML结构相同,降低了react的学习成本,提高了开发效率
-
基本使用体验
// 引入依赖包 import React from 'react'; import ReactDOM from 'react-dom'; // jsx语法糖 用于简化React.createElement()的写法 const ulNode = ( <div> <ul> <li>香蕉</li> <li>火龙果</li> <li>荔枝</li> </ul> </div> ); ReactDOM.render(ulNode, document.querySelector('#root'));
2.2 JSX 注意点
- 只有在脚手架中才能使用 jsx 语法,JSX需要经过 babel 的编译处理,才能在浏览器中使用。脚手架中已经默认有了这个配置。
- JSX必须要有一个根节点,
<></><React.Fragment></React.Fragment> - 元素必须有闭合标记
</> - JSX可以换行,如果JSX有多行,推荐使用
()包裹JSX,防止自动插入分号的bug - 标签属性与js关键词冲突要做替换:
class=====>classNamefor========>htmlFor
2.3JSX插值表达式
-
作用:使标签中可以使用表达式,{表达式}
-
插值表达式 - 基础数据类型
// 引入依赖包 import React from 'react'; import ReactDOM from 'react-dom'; // 当表达式为 布尔类型 / null / undefined 时,标签结构会生成,但不会显示在页面上 const test1 = '测试内容'; const test2 = 123; const test3 = true; const test4 = null; const test5 = undefined; const xmlNode = ( <div> <h2>{test1}</h2> <h2>{test2}</h2> <h2>{test3}</h2> <h2>{test4}</h2> <h2>{test5}</h2> </div> ); //要挂载到真实存在的dom元素上 ReactDOM.render(xmlNode, document.querySelector('#root')); -
插值表达式 - 数组
import React from 'react'; import ReactDOM from 'react-dom'; // 数组的每一项元素会在插值中自动作为一个节点 let list = [<div>1</div>, <div>2</div>, <div>3</div>]; let list2 = [1, 2, 3]; const xmlNode = ( <div> <div>{list}</div> <h2>{list2}</h2> </div> ); ReactDOM.render(xmlNode, document.querySelector('#root')); -
插值表达式 - 对象
import React from 'react'; import ReactDOM from 'react-dom'; // 对象本身不能直接放在插值表达式中,如果属性值不是对象,可以用 对象.属性 语法用在插值表达式 const person = { name: '1354', age: 5, }; const xmlNode = ( <div> <h3>{person.age}</h3> </div> ); ReactDOM.render(xmlNode, document.querySelector('#root')); -
插值表达式 - 函数
// 引入依赖包 import React from 'react'; import ReactDOM from 'react-dom'; //声明变量接收的函数需先声明才能使用, //如果函数返回值是个对象,也可以使用 {testFn().属性名}的方式用在插值表达式中 const testFn = () => { return { name: '5465', }; }; const xmlNode = ( <div> <h4>{testFn().name}</h4> <h4>{tFN().msg}</h4> </div> ); function tFN() { return { msg: '陌上花开,可缓缓归矣', }; } ReactDOM.render(xmlNode, document.querySelector('#root')); -
插值表达式 - jsx本身
// 引入依赖包 import React from 'react'; import ReactDOM from 'react-dom'; //变量可以接收一段jsx文本,然后用在插值表达式中 const jsxNode = <h1>一段jsx文本</h1>; const xmlNode = ( <div> {jsxNode} </div> ); ReactDOM.render(xmlNode, document.querySelector('#root')); -
插值表达式 - 三元表达式与逻辑运算符&&
// 引入依赖包 import React from 'react'; import ReactDOM from 'react-dom'; const age=19 const jsxNode = age > 18 ? <h2>已经成年啦</h2> : <h3>还是小朋友</h3>; const ulNode = ( <div> {age > 18 ? <h2>已经成年啦</h2> : <h3>还是小朋友</h3>} {age > 18 && <h2>已经成年啦</h2>} {jsxNode} </div> ); ReactDOM.render(ulNode, document.querySelector('#root'));
2.4JSX条件渲染
// 引入依赖包
import React from 'react';
import ReactDOM from 'react-dom';
/* 条件渲染
1.三元
2.ifelse --->要通过函数返回值的方式才能使用ifelse做条件渲染
3.逻辑运算符&&
*/
const isshow = true;
const testFn = () => {
if (isshow) {
return <h2>加载完成</h2>;
} else {
return false;
}
};
const testFn1 = () => {
return isshow ? <h2>加载完成</h2> : false;
};
const testFn2 = () => {
return isshow && <h2>加载完成</h2>;
};
const xmlNode = (
<div>
{testFn()}
{testFn1()}
{testFn2()}
</div>
);
ReactDOM.render(xmlNode, document.querySelector('#root'));
2.5列表-数组渲染
// 引入依赖包
import React from 'react';
import ReactDOM from 'react-dom';
const list = ['zs', 'ls', 'lb', 'df'];
const linode = list.map((item, index) => <li key={index}>{item}</li>);
const xmlNode = (
<div>
<ul>{linode}</ul>
{/* 可以直接将map写在插值中 */}
<ul>
{list.map((item, index) => (
<li key={index}>第二遍:{item}</li>
))}
</ul>
</div>
);
ReactDOM.render(xmlNode, document.querySelector('#root'));
2.6 JSX动态样式
// 引入依赖包
import React from 'react';
import ReactDOM from 'react-dom';
import './indexcopy/base.css';
/* 样式
1.行内style:{{CSS属性名:CSS属性值,CSS属性名:CSS属性值}}
2.引入外部样式文件
*/
const sizec = 48;//jsx中 px 单位可以省略
const colorc = '#fff';
const xmlNode = (
<div>
<ul className="list" style={{ fontSize: sizec, color: colorc }}>
<li>1</li>
<li>2</li>
<li>3</li>
</ul>
</div>
);
ReactDOM.render(xmlNode, document.querySelector('#root'));
2.7函数式组件
- 函数组件本质上也是JS函数,只需要在函数中返回一段jsx,
- 函数名必须以大写开头,才能以函数名为标签使用,否则会报错
- 也可以将别的组件以标签的形式嵌套在组件内
// 引入依赖包
import React from 'react';
import ReactDOM from 'react-dom';
const SHe = () => (
<div>
组件一
<h1>君不见黄河之水天上来</h1>
</div>
);
const SayHello = () => (
<div>
组件测试
<h1>君不见高堂明镜悲白发</h1>
{/* 也可以将别的组件以标签的形式嵌套在组件内 */}
<SHe></SHe>
</div>
);
const xmlNode = (
<div>
<SayHello></SayHello>
</div>
);
ReactDOM.render(xmlNode, document.querySelector('#root'));
2.8class组件
- class组件的实质是使用class构造函数去继承react中封装好的 React.Component的对象方法等
// import React from 'react'; --->方式一 不解构引入依赖包
import { Component } from 'react'; //方式二 解构引入依赖包
import ReactDOM from 'react-dom';
/* 方式一 React.Component
class PerNode extends React.Component {
render() {
return <h2>我是一个类组件</h2>;
}
} */
// 方式二 直接使用解构后的 Component
class PerNode extends Component {
render() {
return <h2>我是一个类组件</h2>;
}
}
const xmlNode = (
<div>
<PerNode></PerNode>
</div>
);
ReactDOM.render(xmlNode, document.querySelector('#root'));
2.9 React事件
-
绑定事件:on+驼峰事件名={事件处理函数名}
import React, { Component } from 'react'; export default class App extends Component { render() { return ( <div> {/* 少量代码可以直接写在箭头函数里面 */} <button onClick={() => alert('测试一')}>点击测试一</button> {/* 多行代码可以在 render同级处声明函数,通过this调用,不行写成 this.XXXFn() ,要去掉() */} <button onClick={this.handleClick}>点击测试二</button> </div> ); } handleClick() { alert('测试二'); } } -
获取事件对象与事件传参
/*通过事件函数的默认的形参, 获取事件对象*/ import React, { Component } from 'react'; export default class App extends Component { render() { return ( <div> {/* 通常加上一个箭头, 在箭头函数的方法体内主动传参 */} <a href="http://www.baidu.com" onClick={(e) => this.handleClick(e, '123')} >点我传参</a> </div> ); } handleClick(e, msg) { console.log('e -----> ', e); console.log('msg -----> ', msg); e.preventDefault(); } } -
React中关于this的两种执行情况
/*react中的this分为两种请求 情况1: React自带结构体中, this指向组件实例对象 ✅ 如render函数、 情况2: React非自带结构体中, this执行undefined 🔔 往往需要自行处理 */ import React, { Component } from 'react'; export default class App extends Component { render() { console.log('this -----> ', this);//组件实例 return ( <div> <button onClick={this.handleXxxxx}>点我查看this</button> </div> ); } handleXxxxx() { console.log('this -----> ', this);//undefined } } -
关于this的解决方案
/*解决this指向undefined的常用办法(两种) 原理: 箭头函数没有this, 会自动绑定环境中的this 1. 在render函数内, 事件箭头函数绑定事件 2. 在class内, 用箭头函数定义事件 */ import React, { Component } from 'react'; export default class App extends Component { state = { msg: '123', }; render() { return ( <div> <button // 1. 在render函数内, 事件箭头函数绑定事件 // onClick={() => this.handleXxxxx()} onClick={this.handleXxxxx} > 点我查看this </button> </div> ); } // 2. 定义事件函数时, 用箭头函数定义事件 handleXxxxx = () => { console.log('this -----> ', this); }; }
2.10React状态
- React不允许直接修改state的值, 不能用会会改变原始数据的方法,如: push concat。(推荐: 新值覆盖旧值)
- 在React中引起界面变化唯一的方法就是setState
import React, { Component } from 'react';
export default class App extends Component {
state = {
count: 100,
list: [1, 2, 3],
};
handleAdd = () => {
// ✅ 使用新值覆盖旧值
// this.setState({ count: this.state.count + 1 });
// ✅
this.setState({
list: [...this.state.list, 4],
});
// console.log('this.state.count -----> ', this.state.count);
// ❌ 不能直接修改state的值
// this.state.count++;
// this.state.list.push(4);
// ❌ 会导致修改原始数据都不能用
// this.setState({ list: this.state.list });
};
render() {
return (
<div>
<h1>{this.state.count}</h1>
<h2>{this.state.list}</h2>
<button onClick={this.handleAdd}>点我</button>
</div>
);
}
}
2.11 React表单-受控组件
- 表单元素依赖于状态,表单元素需要默认值实时映射到状态的时候,就是受控组件,这个和vue中的v-model双向绑定相似
- 受控组件只有继承React.Component才会有状态.
- 实现思路:
- state控制表单元素的value或者checked属性
- onChange 配合setState 修改数据
- 受控 / 非受控组件的区别:
- 非受控组件: 表单元素的值不受state的控制, 由dom本身管理
- 受控组件: 不用访问DOM, 更符合数据驱动视图的思想
//受控组件演示
import React, { Component } from 'react';
export default class App extends Component {
state = {username: 'zs',desc: '狂徒',city: '1',isSingle: true,};
//{ target: { name, type, checked, value } }---》直接在参数上将 e.target 解构
handleinputs = ({ target: { name, type, checked, value } }) => {
this.setState({ [name]: type === 'checkbox' ? checked : value });
};
render() {
const { username, desc, city, isSingle } = this.state;
return (
<div>
姓名:
<input name="username" type="text" value={username} onChange={this.handleinputs} />
<br />
描述:
<textarea name="desc" value={desc} onChange={this.handleinputs} ></textarea>
<br />
城市:
<select name="city" value={city} onChange={this.handleinputs}>
<option value="1">北京</option>
<option value="2">上海</option>
<option value="3">广州</option>
<option value="4">深圳</option>
</select>
<br />
是否单身:
<input name="isSingle" type="checkbox" checked={isSingle} onChange={this.handleinputs} />
</div>
);
}
}
2.12 React-ref 获取dom元素或组件实例
-
获取dom元素
import React, { Component } from 'react'; export default class App extends Component { // 1. 创建ref对象 iptRef = React.createRef(); handleClick = () => { // 3. 通过ref对象.current属性获取dom元素 this.iptRef.current.focus(); }; render() { return ( <div> <input // 2. 绑定ref给dom元素 ref={this.iptRef} type="text" /> <button onClick={this.handleClick}>点我看看ipt</button> </div> ); } } -
获取组件
import React, { Component } from 'react'; export default class App extends Component { // 1.React.createRef()调用方法创建组件 childRef = React.createRef(); handleClick = () => { // 3.this.childRef.current属性获取组件对象,可以访问组件里面的方法 this.childRef.current.handleAlert(); }; render() { return ( <div> <button onClick={this.handleClick}>点我获取Child组件</button> {/* 2.将ref绑在组件标签上 */} <Child ref={this.childRef} ></Child> </div> ); } } class Child extends React.Component { state = {count: 100,}; handleAlert() {alert('我是一个大baby');} render() { return <div></div>; } }
2.13组件通信
1. 父传子
/*子组件通过props接收数据
本质: props对象, 所有组件标签身上的属性和值, 组成的一个对象
推荐: 解构props对象
语法:
1. 函数组件, 通过形参接收props
2. class组件,通过this.props接收props
*/
import React, { Component } from 'react';
export default class App extends Component {
render() {
return (
<div>
{/* 组件可以传递任意的数据类型 包括 jsx 函数等 */}
<HelloNode fn={() => alert('传递了一个函数')} testObj={{ name: '狂徒' }} msg="喵喵" count={3} ></HelloNode>
<ChildNode title={<i>传递一段倾斜的文字</i>} msg="芒果" count={16} ></ChildNode>
</div>
);
}
}
//类组件以 this.props接收数据
class HelloNode extends Component {
render() {
const { msg, count, testObj, fn } = this.props;
return (
<div>
<h1>
hello子组件--{msg}-{count}
</h1>
<hr />
<h2>接收的对象属性值----{testObj.name}</h2>
<button onClick={fn}>触发父组件传递的函数</button>
</div>
);
}
}
//函数组件以形参对象 接收传递的数据
function ChildNode({ msg, count, title }) {
return (
<div>
<h2>
child子组件 --{msg}-{count}
</h2>
{title}
</div>
);
}
2. 子传父
/*实现子传父功能
1. 父组件定义一个回调函数
2. 通过props将回调函数传给子组件
3. 子组件内通过props调用父组件传来的函数
*/
import React, { Component } from 'react';
export default class App extends Component {
state = {money: 1000,};
handleMakeMoney = () => {
this.setState({
money: 1000 + this.state.money,
});
};
handleDropMoney = (num) => {
const { money } = this.state;
this.setState({ money: money - num });
};
render() {
const { money } = this.state;
return (
<div>
<h1>总共多少钱:{money}</h1>
<button onClick={this.handleMakeMoney}>爸爸开始赚钱了</button>
<Child dropFn={this.handleDropMoney} money={money}></Child>
</div>
);
}
}
//子组件
class Child extends Component {
render() {
const { money, dropFn } = this.props;
return (
<div>
<h1>爸爸给我钱了:{money} </h1>
{/* 调用父组件的函数时,同时也可以向父组件传递参数 */}
<button onClick={() => dropFn(100)}>买花花:100块</button>
<button onClick={() => dropFn(500)}>买书籍:500块</button>
<button onClick={() => dropFn(50)}>买零食:50块</button>
</div>
);
}
}
3. 跨组件通信-状态提升
/*
状态提升:本质上就是将子组件内的状态, 提升到共同的父组件中
目的: 可以借助于子传父、父传子,实现兄弟组件通信
*/
import React, { Component } from 'react';
//需准备相应的子组件并按路径导入
import Husband from './components/Husband';
import Wife from './components/Wife';
export default class App extends Component {
// 1. state提升到父组件中
state = {money: 0,};
// 2.定义方法
handleMakeMoney = () => {
this.setState({ money: this.state.money + 1000 });
};
// 5. 定义函数钱的函数
handleCost = () => {
this.setState({ money: this.state.money - 5000 });
};
render() {
const { money } = this.state;
return (
<div>
<h1 style={{ textAlign: 'center' }}>家庭存款:{money}</h1>
{/* 3. 父传子传递money给子组件 */}
<Husband handleMakeMoney={this.handleMakeMoney} money={money}></Husband>
<hr />
<Wife
// 6. 通过Props传递函数给wife组件
handleCost={this.handleCost}
></Wife>
</div>
);
}
}
4. 跨组件通信- Context
/*
使用Context来跨组价通信,让App组件-直接传数据给SonSon组件
*/
import React, { Component } from 'react';
// 1. 创建Context, 解构两个组价Provider Consumer
const { Provider, Consumer } = React.createContext();
export default class App extends Component {
render() {
return (
// 2. 使用Provider包住子组件
// 3. 给Provider组件设置value属性,传数据
<Provider value="hello React">
<div>
<h1>父组件</h1>
<Son></Son>
</div>
</Provider>
);
}
}
class Son extends Component {
render() {
return (
<div>
<h2> 儿子</h2>
<SonSon></SonSon>
</div>
);
}
}
class SonSon extends Component {
render() {
return (
// 4. 使用Consumer组件接收数据
<div>
<h2> 孙子</h2>
<Consumer>
{(data) => {
return <h1>123 - {data}</h1>;
}}
</Consumer>
</div>
);
}
}
5. props.children-组件双标签之间的内容
- 通过组件双标签中间区域,也可以向子组件传递数据,子组件可以通过props.children接收
- 双标签之间可以传递任意的数据类型,包括函数,jsx片段等
import React, { Component } from 'react';
export default class App extends Component {
render() {
return (
<div>
<Child>夹在组件标签中间的内容</Child>
<Child>
<i>这是一段jsx,使用了i标签</i>
</Child>
<Child2>{() => alert('组件标签中间也可以传递函数')}</Child2>
</div>
);
}
}
function Child({ children }) {
return <h1>child组件的-----{children}</h1>;
}
function Child2({ children }) {
return (
<h1>
child2组件传递的函数,点击按钮触发
<button onClick={children}>点我触发</button>
</h1>
);
}
6.props类型校验
- 需求场景:当组件对接收的参数有具体的类型要求时,props类型校验就显得很有必要了
/*如何给组件props添加默认值
语法:
1. 组件名.defaultProps = { 属性名:默认值 }
2. 函数值组件最新方法:
function 组件名({属性名= 默认值}){}
💥 PropTypes包会认为没有传默认值
*/
import React, { Component } from 'react';
import PropTypes from 'prop-types';
export default class App extends Component {
render() {
return (
<div>
<Child
name={<h1>React</h1>}
obj={{ name: 'zs', age: 18 }}
fn={() => {}}
isShow={true}
title={123}
></Child>
<Child2 title={123}></Child2>
</div>
);
}
}
class Child extends React.Component {
render() {
const { title, color } = this.props;
return (
<div>
{title.toFixed(2)} - {color}
</div>
);
}
}
//给参数设置默认值
Child.defaultProps = {
color: 'red',
};
function Child2({ color = 'green' }) {
return <h2>Child2 - {color}</h2>;
}
Child2.propTypes = {
title: PropTypes.number,
color: PropTypes.string.isRequired,
isShow: PropTypes.bool,
fn: PropTypes.func,
obj: PropTypes.shape({
name: PropTypes.string,
age: PropTypes.number,
}),
name: PropTypes.element,
};
// 2. 给组件设置检验规则
Child.propTypes = {
title: PropTypes.number,
color: PropTypes.string.isRequired,
isShow: PropTypes.bool,
fn: PropTypes.func,
obj: PropTypes.shape({
name: PropTypes.string,
age: PropTypes.number,
}),
name: PropTypes.element,
};
7.关于static关键词
// class成员:分为两类
// 1. 实例成员: 直接写在class中的属性和方法,而且没有static
// 特点: 只能通过实例对象.xxx去访问
// 2. 静态成员: class中有static关键字的属性和方法
// 特点: 只能通过类名.xxx去访问
// static关键字,作用:将一个实例成员,变为静态成员
import PropTypes from 'prop-types';
import React, { Component } from 'react';
export default class App extends Component {
render() {
return (
<div>
<Child title={''}></Child>
</div>
);
}
}
class Child extends React.Component {
static defaultProps = {
color: 'red',
};
static propTypes = {
title: PropTypes.number,
color: PropTypes.string.isRequired,
isShow: PropTypes.bool,
fn: PropTypes.func,
obj: PropTypes.shape({
name: PropTypes.string,
age: PropTypes.number,
}),
name: PropTypes.element,
};
render() {
const { color } = this.props;
return <div>{color}</div>;
}
}
2.14React生命周期与钩子函数
import React, { Component } from 'react';
export default class App extends Component {
// 不要在constructor函数中发送请求
constructor() {
super();
console.log('先触发constructor');
}
state = {
count: 0,
};
// 渲染函数
render() {
const { count } = this.state;
console.log('再触发render');
return (
<div>
App
<button onClick={() => this.setState({ count: count + 1 })}>
点击了{count}
</button>
</div>
);
}
// 类似于 vue中的created和mounted的统合使用
// 页面初始化发送数据请求一般放到这个钩子函数
componentDidMount() {
console.log('componentDidMount,组件挂载后触发');
}
// 当state的数据或 props的数据发生更新时,会触发该函数
//prevProps回调参数可以捕获到更新前的props对象, prevState回调参数可以捕获到更新前的state对象
componentDidUpdate(prevProps, prevState) {
console.log(prevProps, prevState);
console.log(30, this.state);
console.log('页面初始化不触发,state的数据或 props的数据发生更新时才触发');
}
}
2.15 setstate的几种写法
-
setState({}) 场景:最常用,不需要连续调用setState,用对象即可最简单 特点: 连续调用,会产生覆盖效果
-
setState((旧的state) => 新的state)
场景:需要根据上一次的计算结果, 计算下一次的值 特点:连续调用,会产生串联等待上次计算结果的效果
-
setState({}, () => {})
场景: 需要根据上一次的计算结果, 计算下一次的值 特点: 不会产生合并效果, 容易浪费性能。 2. 容易回调地域
写法一:setState(更新的state),**
特点:这种写法连续调用setState, 最后一次setState会覆盖前面的setState
//目的: React为了减少操作dom的次数, 节省性能
import React, { Component } from 'react';
export default class App extends Component {
state = {
count: 0,
};
handleClick = () => {
this.setState({ count: this.state.count + 1 });
this.setState({ count: this.state.count + 2 });
this.setState({ count: this.state.count + 3 });//不执行+1+2,只执行最后一次的+3
console.log('this.state.count ----->', this.state.count);//打印的是 0,更新前的值
};
render() {
const { count } = this.state;
console.log('render被触发了 -----> ');
return (
<div>
<h1>{count}</h1>
<button onClick={this.handleClick}>点我+1</button>
</div>
);
}
}
写法二: setState((旧的state) => 更新的state )
特点:这种写法下连续调用setstate,前一个setstate里返回的数据会依次传递给下一个setstate的函数形参prestate,会在最后一次调用时将多次计算后的值赋给state
import React, { Component } from 'react';
export default class App extends Component {
state = {
count: 0,
};
handleClick = () => {
this.setState((preState) => {
console.log(this.state.count);//0
console.log(preState.count);//0
return {
count: preState.count + 1,
};
});
this.setState((preState) => {
console.log(this.state.count);//0
console.log(preState.count);//1
return {
count: preState.count + 2,
};
});
this.setState((preState) => {
console.log(this.state.count);//0
console.log(preState.count);//3
return {
count: preState.count + 3,
};
});
};
render() {
const { count } = this.state;
console.log('render执行几次 -----> ');//只打印一次
return (
<div>
<h1>{count}</h1>
<button onClick={this.handleClick}>点我+1</button>
</div>
);
}
}
写法三: setState(更新的state , () => {}) ,参数二的函数会在值更新后触发
特点:如果在setstate的回调函数中再次调用setstate, 不会形成合并, 不会减少dom更新次数,会导致回调地域问题,消耗性能(不推荐)。
import React, { Component } from 'react';
export default class App extends Component {
state = {
count: 0,
};
handleClick = () => {
this.setState({ count: this.state.count + 1 }, () => {
this.setState({ count: this.state.count + 2 }, () => {
this.setState({ count: this.state.count + 3 });
});
});
};
render() {
const { count } = this.state;
console.log('render执行几次 -----> ');//会执行三次
return (
<div>
<h1>{count}</h1>
<button onClick={this.handleClick}>点我+1</button>
</div>
);
}
}
2.16React的更新机制-组件
/*默认情况下:
1. 父组件更新, 所有后代全部更新
2. 组件更新, 不会导致兄弟组件更新
*/
import React, { Component } from 'react';
export default class App extends Component {
state = {
appCount: 0,
};
handleAppAdd = () => {
this.setState({ appCount: this.state.appCount + 1 });
};
render() {
return (
<div>
<h1>{this.state.appCount}</h1>
{/* 父组件状态更新时,所有子组件的render都会触发 */}
<button onClick={this.handleAppAdd}>点击更新父组件的state</button>
<Child></Child>
<Child2></Child2>
</div>
);
}
}
class Child extends React.Component {
state = {
count: 100,
};
handleAdd = () => {
this.setState({ count: this.state.count + 1 });
};
render() {
console.log('Child被触发更新了 -----> ');
return (
<div>
Child
{/* 子组件的状态更新时,只触发自身的render,兄弟组件不会触发render, */}
<button onClick={this.handleAdd}>点我+1</button>
</div>
);
}
}
class Child2 extends React.Component {
render() {
console.log('Child2被触发更新了 -----> ');
return <div>Child2</div>;
}
}
使用shouldComponentUpdate() 进行更新优化
import React, { Component } from 'react';
export default class App extends Component {
state = {
count: 100,
msg: 'hello React',
};
handleAdd = () => {
this.setState({ count: this.state.count + 1 });
};
handleAddMsg = () => {
this.setState({ msg: this.state.msg + '~' });
};
render() {
const { count, msg } = this.state;
return (
<div>
<h1>{count}</h1>
<h1>{msg}</h1>
<button onClick={this.handleAdd}>点我+1</button>
<button onClick={this.handleAddMsg}>点我+~</button>
<Child count={count}></Child>
</div>
);
}
}
class Child extends React.Component {
//nextProps形参可以拿到新的state,可以利用新旧值得对比,决定返回 true 或 false
//当shouldComponentUpdate函数返回一个 false 组件就不会再更新 为true则会继续更新
shouldComponentUpdate(nextProps) {
if (nextProps.count !== this.props.count) {
return true;
}
return false;
}
render() {
console.log('Child被更新了 -----> ');
return <div>Child - {this.props.count}</div>;
}
}
使用React.PureComponent 纯组件进行更新优化(不要滥用)
import React, { Component } from 'react';
export default class App extends Component {
state = {
count: 0,
msg: 'hello',
};
render() {
return (
<div>
App
<button onClick={() => this.setState({ count: this.state.count + 1 })}>
点我+1
</button>
<button onClick={() => this.setState({ msg: this.state.msg + '~' })}>
点我文字加~
</button>
<Child count={this.state.count}></Child>
</div>
);
}
}
// React.PureComponent 纯组件,会将preprops和nextprops进行浅比较,只比较数据的引用地址,不会比较值
// 这就是为什么react一再强条的状态不可变,要使用新值覆盖旧值
class Child extends React.PureComponent {
render() {
console.log('触发子组件render');
return <div>child1</div>;
}
}
3.路由
3.1js模拟路由
import React from 'react';
export default class App extends React.Component {
state = {
currentHash: '/home',
};
componentDidMount() {
// 挂载后获取url赋值一次
this.setState({ currentHash: window.location.hash.slice(1) });
window.addEventListener('hashchange', () => {
// 监听hashchange事件,并更新状态
console.log(window.location);
this.setState({ currentHash: window.location.hash.slice(1) });
});
}
render() {
const { currentHash } = this.state;
return (
<div>
<h1>app组件</h1>
<ul>
<li>
<a href="#/home">首页</a>
</li>
<li>
<a href="#/my">我的音乐</a>
</li>
<li>
<a href="#/friend">我的朋友</a>
</li>
</ul>
{/* 根据状态对组件条件渲染 */}
{currentHash === '/home' && <Home></Home>}
{currentHash === '/my' && <MyMusic></MyMusic>}
{currentHash === '/friend' && <Friend></Friend>}
</div>
);
}
}
function Home() {
return <h1>我是首页组件</h1>;
}
function MyMusic() {
return <h1>我是我的音乐件</h1>;
}
function Friend() {
return <h1>我是朋友组件</h1>;
}
3.2React路由的基本使用
/*步骤:
1. 下包 react-router-dom@5.3
2. 导入三个组件HashRouter Route Link
HashRouter
作用: 实例化路由, 💥全局只调用一次
类似: Vue中的new VueRouter
注意: 包住全部的代码
Route组件
作用: 设置路由规则, 作为挂载点
类似: vue中的规则对象 和 挂载点 二合一
Link组价:
作用:跳转路由 to="/路径"
本质: a标签 router-link
*/
import React, { Component } from 'react';
import { HashRouter, Route, Link } from 'react-router-dom';
export default class App extends Component {
render() {
// 3. 使用HashRouter包住所有的代码 -
return (
<HashRouter>
{/* 5. 通过Link组件去跳转 */}
<Link to="/home">点我调到首页</Link>
<br />
<Link to="/my">点我调到我的</Link>
<br />
<Link to="/friend">点我调到朋友</Link>
<div>
{/* 4. 设置路由规则: Route组件设置
*/}
<div className="my_home">
<Route path="/home" component={Home}></Route>
</div>
<Route path="/my" component={MyMusic}></Route>
<Route path="/friend" component={Friend}></Route>
</div>
</HashRouter>
);
}
}
function Home() {
return <h1>我是首页组件</h1>;
}
function MyMusic() {
return <h1>我是我的音乐件</h1>;
}
function Friend() {
return <h1>我是朋友组件</h1>;
}
3.3路由模式切换
import React, { Component } from 'react';
// HashRouter 哈希路由模式
// BrowserRouter 历史路由模式
// 推荐通过as 将路由重命名为Router ,方便后期修改路由模式
import { BrowserRouter as Router, Route, Link } from 'react-router-dom';
export default class App extends Component {
render() {
return (
<Router>
<Link to="/home">点我跳到首页</Link>
<br />
<Link to="/my">点我跳到我的</Link>
<br />
<Link to="/friend">点我跳到朋友</Link>
<div>
<div className="my_home">
<Route path="/home" component={Home}></Route>
</div>
<Route path="/my" component={MyMusic}></Route>
<Route path="/friend" component={Friend}></Route>
</div>
</Router>
);
}
}
function Home() {
return <h1>我是首页组件</h1>;
}
function MyMusic() {
return <h1>我是我的音乐件</h1>;
}
function Friend() {
return <h1>我是朋友组件</h1>;
}
3.4NavLink的使用
- NavLink标签与Link标签的区别在于,可以方便设置点击样式
- NavLink链接点击时,会添加一个'active'类名,可通过设置active类的样式来定义被点击的NavLink标签样式
- 当需要设置自定义类名作为点击样式时,可通过NavLink标签的activeClassName属性设置自定义active类名
import React, { Component } from 'react';
import {
BrowserRouter as Router,
Route,
// Link,
NavLink,//导入NavLink组件
} from 'react-router-dom';
import './apptest.css';//导入自定义样式表
export default class App extends Component {
render() {
return (
<Router>
{/* <Link to="/home">跳转到首页</Link> */}
<NavLink to="/home" activeClassName="testactive">
跳转到首页
</NavLink>
<br />
{/* <Link to="/my">我的音乐</Link> */}
<NavLink to="/my" activeClassName="testactive">
我的音乐
</NavLink>
<br />
{/* <Link to="/friend">跳转到朋友</Link> */}
<NavLink to="/friend" activeClassName="testactive">
跳转到朋友
</NavLink>
<br />
<Route path="/home" component={Home}></Route>
<Route path="/my" component={MyMusic}></Route>
<Route path="/friend" component={Friend}></Route>
</Router>
);
}
}
function Home() {
return <h1>我是首页组件</h1>;
}
function MyMusic() {
return <h1>我是我的音乐件</h1>;
}
function Friend() {
return <h1>我是朋友组件</h1>;
}
3.5Route组件的说明
- Route 组件的作用相当于 路由规则和挂载点的集合
- Route 组件默认模糊匹配,只要url包含 (to 或path) 的值,对应组件就会显示
- Route组件添加 exact 表示精准匹配 url严格等于 to或path;
import React, { Component } from 'react';
import {
BrowserRouter as Router,
Route,
NavLink,
} from 'react-router-dom';
import './apptest.css';
export default class App extends Component {
render() {
return (
<Router>
{/* 添加 exact 表示精准匹配 url严格等于 to或path;
模糊匹配模式下,只要url包含 to 或path 对应组件就会显示*/}
<NavLink to="/home" activeClassName="testactive">
跳转到首页
</NavLink>
<br />
<NavLink to="/my" activeClassName="testactive">
我的音乐
</NavLink>
<br />
<NavLink to="/friend" activeClassName="testactive">
跳转到朋友
</NavLink>
<br />
{/* Route 组件的作用相当于 路由规则和挂载点的集合 */}
{/* Route组件添加 exact 表示精准匹配 url严格等于 to或path;
模糊匹配模式下,只要url包含 to 或path 对应组件就会显示*/}
{/* 模糊匹配下 url='/home/1234' 此时path='/home'的组件也会显示 */}
<Route path="/home" component={Home}></Route>
<Route path="/my" component={MyMusic} exact></Route>
<Route path="/friend" component={Friend}></Route>
</Router>
);
}
}
function Home() {
return <h1>我是首页组件</h1>;
}
function MyMusic() {
return <h1>我是我的音乐件</h1>;
}
function Friend() {
return <h1>我是朋友组件</h1>;
}
3.6 Switch组件
- Switch组件的特点: 匹配到任意一个,即停止向下匹配
import React, { Component } from 'react';
import './index.css';
// 👍通过as 将路由重命名为Router
import { BrowserRouter as Router, Route, Link, NavLink, Switch } from 'react-router-dom';
export default class App extends Component {
render() {
return (
<Router>
<NavLink to="/home/123" activeClassName="xxx" exact>
点我调到首页
</NavLink>
<br />
<NavLink to="/my">点我调到我的</NavLink>
<br />
<NavLink to="/friend">点我调到朋友</NavLink>
<div>
{/* Switch组件的特点: 匹配到任意一个,即停止向下匹配 */}
{/* 👍 使用Route组件,直接包在Switch组件中 */}
<Switch>
{/* Switch组件包裹时,即使有三个home组件的挂载点,也只会在匹配到第一个后就不往后匹配 */}
<Route path="/home" component={Home}></Route>
<Route path="/home" component={Home}></Route>
<Route path="/home" component={Home}></Route>
<Route path="/my" component={MyMusic}></Route>
<Route path="/friend" component={Friend}></Route>
{/* 👍不写path 通常放在Switch组件的最后一个,表示兜底,匹配任意路径 */}
{/* 场景:通常用来做404页面 */}
<Route component={NotFound}></Route>
</Switch>
</div>
</Router>
);
}
}
function NotFound() {
return <div>404页面</div>;
}
function Home() {
return <h1>我是首页组件</h1>;
}
function MyMusic() {
return <h1>我是我的音乐件</h1>;
}
function Friend() {
return <h1>我是朋友组件</h1>;
}
3.7 路由嵌套
- 二级路由的路由配置等定义在一级路由组件中
import React, { Component } from 'react';
import './index.css';
import { BrowserRouter as Router, Route, Link, NavLink, Switch } from 'react-router-dom';
export default class App extends Component {
render() {
return (
<Router>
<NavLink to="/home/123" activeClassName="xxx" exact>
点我调到首页
</NavLink>
<br />
<NavLink to="/my">点我调到我的</NavLink>
<br />
<NavLink to="/friend">点我调到朋友</NavLink>
<div>
<Switch>
<Route path="/home" component={Home}></Route>
<Route path="/my" component={MyMusic}></Route>
{/*嵌套路由中父组件一般不能使用exact,会导致匹配不到页面组件 */}
<Route path="/friend" component={Friend}></Route>
<Route component={NotFound}></Route>
</Switch>
</div>
</Router>
);
}
}
function Friend() {
return (
<div>
<h1>我是朋友组件</h1>
{/* 在父组件中再使用一次Switch + Route */}
{/* 嵌套路由中: 父子级路径可以同名。 两个组件都会显示 */}
{/* 💥 注意:1. 嵌套路由中不需要在使用Router组件 */}
{/* 💥 注意:2. 嵌套路由中父组件一般不能使用exact */}
{/* 💥 注意:3. 跳转路由的Link组件,要从/一级路径开始写 */}
<Link to="/friend/friend1">调到朋友1</Link>
<Link to="/friend/friend2">调到朋友2</Link>
<Link to="/friend/friend3">调到朋友3</Link>
<Switch>
<Route path="/friend/friend1" component={Friend1}></Route>
<Route path="/friend/friend2" component={Friend2}></Route>
<Route path="/friend/friend3" component={Friend3}></Route>
</Switch>
</div>
);
}
function NotFound() {
return <div>404页面</div>;
}
function Home() {
return <h1>我是首页组件</h1>;
}
function MyMusic() {
return <h1>我是我的音乐件</h1>;
}
function Friend1() {
return <i>我是朋友子组件1</i>;
}
function Friend2() {
return <i>我是朋友子组件2</i>;
}
function Friend3() {
return <i>我是朋友子组件3</i>;
}
3.8 Redirect重定向组件
- 应用场景:重定向到首页 / 重定向到登录界面
- Redirect 通常配合exact使用
- path属性与from属性一致表示从哪个页面来,to属性定义重定向的页面
import React, { Component } from 'react';
import './index.css';
import { BrowserRouter as Router, Route, Link, NavLink, Switch, Redirect } from 'react-router-dom';
export default class App extends Component {
render() {
return (
<Router>
<NavLink to="/home/123" activeClassName="xxx" exact>
点我调到首页
</NavLink>
<br />
<NavLink to="/my">点我调到我的</NavLink>
<br />
<NavLink to="/friend">点我调到朋友</NavLink>
<div>
<Switch>
{/* 💥 场景: 1. 重定向到首页 2. 重定向到登录界面 */}
{/* 💥 Redirect 通常配合exact使用 */}
<Redirect path="/" to="/home" exact></Redirect>
<Route path="/home" component={Home}></Route>
<Route path="/my" component={MyMusic}></Route>
<Route path="/friend" component={Friend}></Route>
<Route component={NotFound}></Route>
</Switch>
</div>
</Router>
);
}
}
function Friend() {
return (
<div>
<h1>我是朋友组件</h1>
</div>
);
}
function NotFound() {
return <div>404页面</div>;
}
function Home() {
return <h1>我是首页组件</h1>;
}
function MyMusic() {
return <h1>我是我的音乐件</h1>;
}
3.9动态路由-路由传参
- 改造Route组件的path属性 语法:path="/路径/:自定义属性名
- 页面组件中通过props.match.params.自定义属性名, 获取动态路由的参数值
import React, { Component } from 'react';
import './index.css';
import { BrowserRouter as Router, Route, Link, NavLink, Switch, Redirect } from 'react-router-dom';
export default class App extends Component {
render() {
return (
<Router>
<NavLink to="/my">点我调到我的</NavLink>
<div>
<Switch>
{/* 1. 改造路由的path属性 语法:path="/路径/:自定义属性名" */}
<Route path="/my/:id" component={MyMusic}></Route>
</Switch>
<Header></Header>
</div>
</Router>
);
}
}
function MyMusic({ match }) {
// 2. 通过props.match.params.自定义属性名, 获取动态路由的参数值
console.log('params -----> ', match.params.id);
return <h1>我是我的音乐件 </h1>;
}
3.10编程式导航-路由跳转
- 实现原理:Route组件设置过匹配规则的组件有history对象,该对象包含push("/路径") / go(数字) / goBack()回退等路由跳转方法
- 注意:只有Route组件设置过匹配规则的组件才有history对象,普通组件没有history
/*编程式导航-通过JS跳转路由
语法: props.history操作路由跳转
常用: 1. push("/路径") 2. go(数字) 3. goBack()回退
注意: 只有Route组件设置过匹配规则的组件才有history对象,普通组件没有history
*/
import React, { Component } from 'react';
import './index.css';
import { BrowserRouter as Router, Route, Link, NavLink, Switch, Redirect } from 'react-router-dom';
export default class App extends Component {
render() {
return (
<Router>
<NavLink to="/home/123" activeClassName="xxx" exact>
点我调到首页
</NavLink>
<br />
<NavLink to="/my">点我调到我的</NavLink>
<br />
<NavLink to="/friend">点我调到朋友</NavLink>
<div>
<Switch>
<Redirect path="/" to="/home" exact></Redirect>
<Route path="/home" component={Home}></Route>
<Route path="/my" component={MyMusic}></Route>
<Route path="/friend" component={Friend}></Route>
<Route component={NotFound}></Route>
</Switch>
<Header></Header>
</div>
</Router>
);
}
}
function Friend() {
return (
<div>
<h1>我是朋友组件</h1>
<Link to="/friend">调到朋友1</Link>
<Link to="/friend/friend2">调到朋友2</Link>
<Link to="/friend/friend3">调到朋友3</Link>
<Switch>
<Route path="/friend" component={Friend1} exact></Route>
<Route path="/friend/friend2" component={Friend2}></Route>
<Route path="/friend/friend3" component={Friend3}></Route>
</Switch>
</div>
);
}
function Header(params) {
return <h1>Header</h1>;
}
function NotFound() {
return <div>404页面</div>;
}
class Home extends React.Component {
handleClick = () => {
this.props.history.push('/my');
//this.props.history.go(1);
};
render() {
return (
<div>
<h1>我是首页组件</h1>
<button onClick={this.handleClick}>点我跳转</button>
</div>
);
}
}
function MyMusic({ history }) {
return (
<h1>
我是我的音乐件{' '}
<button
onClick={() => {
// history.go(-1);
history.goBack();
}}
>
点我后退
</button>
</h1>
);
}
function Friend1() {
return <i>我是朋友组件1</i>;
}
function Friend2() {
return <i>我是朋友组件2</i>;
}
function Friend3() {
return <i>我是朋友组件3</i>;
}