一、极速入门
1.基本概念
- 1.什么是React
- React 起源于 Facebook 内部项目,因为Facebook对市场上所有 JavaScript MVC 框架,都不满意, 就决定自己写一个框架,用来架设 Instagram 的网站
- 来源: https://reactjs.org/blog/2013/06/05/why-react.html
- 2.什么是框架
- 框架是一个半成品,已经对基础的代码进行了封装并提供相应的API
- 开发者在使用框架时,可以复用框架中封装好的代码,从而提高工作效率
- 框架就是毛坯房, 已经帮助我们搭建好了基本的架子, 我们只需要拿过来根据
- 我们自己的需求装修即可
- 3.为什么要学习框架
- 提升开发效率
- 4.React核心思想
- 数据驱动界面更新:只要数据发生了改变, 界面就会自动改变
- 组件化开发:将网页拆分成一个个独立的组件来编写,然后再将编写好的组件,拼接成 一个完整的网页
- 5.什么是虚拟DOM
- 真实DOM:是相对于浏览器所渲染出来的真实 DOM 的
- 虚拟DOM:就是使用JS对象来表示页面上真实的 DOM
<div id="name" title= "name"> // 真实的DOM
let obj = { // 虚拟DOM
tagName: 'div',
attrs:{
id: "name" ,
title: "name"
}
2.基本使用
- 1.使用React的几种方式
- 自行配置
- https://zh-hans.reactjs.org/docs/add-react-to-a-website.html
- 通过脚手架自动配置
- https://zh-hans.reactjs.org/docs/create-a-new-react-app.html
- 自行配置
- 自行配置
- 引入 react.development.v17.js 和 react-dom.development.v17.js
- 可以将其先下载下来,然后再导入
- 或者直接在线导入
<script src="https://unpkg.com/react@17/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@17/umd/react-dom.development.js" crossorigin>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<!-- 引入react与react-dom -->
<script src="react17/react.development.v17.js"></script>
<script src="react17/react-dom.development.v17.js"></script>
</head>
<body>
<div id="app"></div>
<script>
// 1.创建虚拟DOM
// <div>知播渔</div>
let message = '知播渔';
// 参数解释:标签元素/组件 属性 内容
let oDiv = React.createElement('div', null, message);
// 2.将虚拟DOM转换成真实DOM
// 参数解释:被渲染的虚拟DOM 要渲染到哪个元素中 渲染或更新完成后的回调函数
ReactDOM.render(oDiv, document.getElementById("app"), ()=>{
console.log('已经将虚拟DOM转换成了真实DOM, 已经渲染到界面上了');
});
</script>
</body>
</html>
- 2.react.js 和 react-dom.js
- react.js
- 包含了React和React-Native所共同拥有的核心代码
- 主要用于生成虚拟DOM
- react-dom.js
- 包含了针对不同平台渲染不同内容的核心代码
- 主要用于将虚拟DOM转换为真实DOM
- 简而言之
- 利用react.js编写界面 (创建虚拟DOM)
- 利用react-dom.js渲染界面 (创建真实DOM)
- react.js
- 3.React如何创建DOM元素
- 在React中, 我们不能通过 HTML标签 直接创建DOM元素
- 在React中, 我们必须先通过react.js创建虚拟DOM, 再通过react-dom.js渲染元素 (转换为真实DOM)
- 4.如何通过react.js创建虚拟DOM
- 通过React.createElement()方法
- 该方法接收3个参数
- 第一个参数: 需要创建的元素类型或组件
- 第二个参数: 被创建出来的元素拥有的属性
- 第三个参数: 被创建出来的元素拥有的内容 (可以是多个)
- createElement 注意点
- 可以添加3个以上参数, 后续参数都会作为当前创建元素内容处理
- 5.如何通过react-dom.js渲染虚拟DOM
- 通过ReactDOM.render()方法
- 该方法接收3个参数
- 第一个参数: 被渲染的虚拟DOM
- 第二个参数: 要渲染到哪个元素中
- 第三个参数: 渲染或更新完成后的回调函数
- 方法注意点
- 多次渲染, 后渲染会覆盖先渲染
- render 方法一次只能渲染一个元素/组件
- 6.如何给元素添加监听
- 给元素添加监听的本质就是给元素添加属性
- 所以可以在createElement()的第二个参数中添加
- 按钮
- React.createElement('button', {onClick: btnClick}, '按钮');
- 注意事项: 如果想给元素绑定事件, 那么事件名称必须是驼峰命名
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<!-- 引入react和react-dom -->
<script src='../react17/react.development.v17.js'></script>
<script src='../react17/react-dom.development.v17.js'></script>
</head>
<body>
<div id="app"></div>
<script>
// 1.创建虚拟DOM
let message = '知播渔';
// <div>知播渔</div>
let oDiv = React.createElement('div', null, message);
// <button onclick='myfn'>按钮</button>
let oBtn = React.createElement('button', null, '按钮');
// 注意点: 如果想通过React绑定事件, 那么事件名称必须是驼峰命名
let oRoot = React.createElement('div', { onClick: myFn }, oDiv, oBtn);
// 2.将虚拟DOM转换成真实DOM
ReactDOM.render(oRoot, document.getElementById('app'), () => {
console.log('已经将虚拟DOM转换成了真实DOM, 已经渲染到界面上了');
});
function myFn() {
message = 'www.it666.com';
// 注意点: 默认情况下载React中, 修改完数据之后, 是不会自动更新界面的
let oDiv = React.createElement('div', null, message);
let oBtn = React.createElement('button', null, '按钮');
let oRoot = React.createElement('div', { onClick: myFn }, oDiv, oBtn);
ReactDOM.render(oRoot, document.getElementById('app'), () => {
console.log('已经将虚拟DOM转换成了真实DOM, 已经渲染到界面上了');
});
}
</script>
</body>
</html>
3.JSX
- 1.通过createElement创建元素存在的问题
- 如果结构比较简单还好, 但是如果结构比较复杂, 就比较难以下手
- 所以大牛们就发明了JSX, 专门用来编写React中的页面结构体
- 2.什么是JSX
- JSX === JavaScript + X === (XML) === (eXtension)
- JSX 是一个看起来很像 XML 的 JavaScript 语法扩展
- 3.为什么要使用JSX
- 使用JSX使得我们在React中编写页面结构更为简单、灵活
- JSX 是类型安全的,在编译过程中就能发现错误
- JSX 执行更快,因为它在编译为 JavaScript 代码后进行了优化
- 防止XSS注入攻击
- 文档地址:https://zh-hans.reactjs.org/docs/introducing-jsx.html
- 4.JSX本质
- 浏览器只认识JS不认识JSX, 所以我们编写的JSX代码是无法在浏览器中执行的
- 为了解决这个问题, 我们需要借助babel将JSX转换成JS, 也就是转换成React.createElement();
- 5.如何在项目中将JSX转换成JS
- 引入 babel.min.js
- 在 script 标签上,添加 type="text/babel"
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<!-- 引入react和react-dom -->
<script src='../react17/react.development.v17.js'></script>
<script src='../react17/react-dom.development.v17.js'></script>
<script src='../react17/babel.min.js'></script>
</head>
<body>
<div id="app"></div>
<script type="text/babel">
let message = '知播渔';
// 优化:将其封装为一个函数,后期直接调用
function myRender(){
// let oDiv = React.createElement('div', null, message);
// let oBtn = React.createElement('button', null, '按钮');
// let oRoot = React.createElement('div', { onClick: myFn }, oDiv, oBtn);
// JSX 写法
let oRoot = (
<div>
<div>{message}</div>
<button onClick={myFn}>按钮</button>
</div>
)
ReactDOM.render(oRoot, document.getElementById('app'), () => {
console.log('已经将虚拟DOM转换成了真实DOM, 已经渲染到界面上了');
});
}
myRender();
function myFn() {
message = 'www.it666.com';
myRender();
}
</script>
</body>
</html>
4.组件的定义
- 1.在React中如何定义组件
- 在React中创建组件有两种方式
- 第一种:通过ES6之前的构造函数来定义(无状态/数据组件)
- 第二种:通过ES6开始的class类来定义(有状态/数据组件)
- 在React中创建组件有两种方式
- 2.如何通过ES5的构造函数来定义组件
- 在构造函数中返回组件的结构即可
function Home() {
return (
<div>
<div>{message}</div>
<button onClick={btnClick}>按钮</button>
</div>
);
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<!-- 引入react和react-dom -->
<script src='../react17/react.development.v17.js'></script>
<script src='../react17/react-dom.development.v17.js'></script>
<script src='../react17/babel.min.js'></script>
</head>
<body>
<div id="app"></div>
<script type="text/babel">
let message = '知播渔';
// 定义函数组件
function Home(){
return (
<div>
<div>{message}</div>
<button onClick={myFn}>按钮</button>
</div>
)
}
// <Home/>:调用组件
ReactDOM.render(<Home/>, document.getElementById('app'));
function myFn() {
message = 'www.it666.com';
ReactDOM.render(<Home/>, document.getElementById('app'));
}
</script>
</body>
</html>
- 3.如何通过ES6的class来定义组件
- 定义一个类, 在这个类中实现 render方法, 在render方法中返回组件的结构即可
class Home extends React.Component{
render(){
return (
<div>
<div>{message}</div>
<button onClick={btnClick}>按钮</button>
</div>
)
}
}
5.组件的状态
- 1.有状态组件和无状态组件
- 首先需要明确的是, 组件中的状态(state)指的其实就是数据
- 有状态组件:指的就是有自己数据的组件(逻辑组件)
- 无状态组件:指的就是没有自己数据的组件(展示组件)
- 首先需要明确的是, 组件中的状态(state)指的其实就是数据
- 2.如何定义自己的状态
- 凡是继承于React.Component的组件, 默认都会从父类继承过来一个state属性
- 这个state属性就是专门用来保存当前数据的
- 所以但凡是继承于React.Component的组件, 都是有状态组件
- 所以但凡不是继承于React.Component的组件, 都是无状态组件
- 所以类组件就是有状态组件
- 所以函数组件就是无状态组件
6.this指向
- 1.this指向问题
- 在ES6之前, 方法中的this谁调用就是谁,并且还可以通过call/apply/bind方法修改this
- 从ES6开始, 新增了箭头函数, 箭头函数没有自己的this,箭头函数中的this是函数外最近的那个this,并且由于箭头函数没有自己的this, 所以不能通过call/apply/bind方法修改this
- 2.监听事件中的this
- React内部在调用监听方法的时候, 默认会通过apply方法将监听方法的this修改为了undefined,所以在监听方法中无法通过this拿到当前组件的state. (undefined.state)
- 如果想在监听方法中拿到当前组件的state, 那么就必须保证监听方法中的this就是当前实例,所以我们可以借助箭头函数的特性, 让React无法修改监听方法中的this, 让监听方法中的this就是当前实例
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<!-- 引入react和react-dom -->
<script src='../react17/react.development.v17.js'></script>
<script src='../react17/react-dom.development.v17.js'></script>
<script src='../react17/babel.min.js'></script>
</head>
<body>
<div id="app"></div>
<script type="text/babel">
// let message = '知播渔';
// 定义类组件
class Home extends React.Component {
constructor(){
// 继承父类的方法与属性
super();
// stat:专门用于定义数据的
this.stat = {
message: '知播渔'
};
}
render() {
return (
<div>
<div>{this.stat.message}</div>
<button onClick={this.myFn}>按钮</button>
</div>
)
}
myFn = () => {
// 注意点: React在调用监听方法的时候, 会通过apply修改监听方法的this
// 所以在普通的方法中, 我们拿到的this是undefined,
// 所以我们无法在普通的方法中通过this拿到当前组件的state
// 解决方案:使用箭头函数,让React无法修改监听方法中的this
// console.log(this); // undefined
this.stat.message = 'www.it666.com';
ReactDOM.render(<Home />, document.getElementById('app'));
}
}
// <Home/>:调用组件
ReactDOM.render(<Home />, document.getElementById('app'));
</script>
</body>
</html>
7.state属性
- state属性注意点
- 永远不要直接修改state,直接修改state并不会触发界面更新
- 只有使用setState方法,修改state,才会触发界面更新
myFn = () => {
// 注意点:
// 如果要修改state中的数据, 那么永远不要直接修改
// 如果要修改state中的数据, 那么需要通过setState方法来修改
// this.state.message = 'www.it666.com';
// console.log(this.state.message);
this.setState({
message: 'www.it666.com'
})
//ReactDOM.render(<Home/>, document.getElementById('app'));
}
案例:购物车
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<!-- 引入react和react-dom -->
<script src='../react17/react.development.v17.js'></script>
<script src='../react17/react-dom.development.v17.js'></script>
<script src='../react17/babel.min.js'></script>
</head>
<body>
<div id="app"></div>
<!-- 需求:购物车 (增加 减少) -->
<script type="text/babel">
// 定义类组件
class Counter extends React.Component{
// 定义数据
constructor(){
super();
this.state = {
count: 0
};
}
// 搭建结构
render(){
return(
<div>
<button onClick={this.sub}>减少</button>
<span>{this.state.count}</span>
<button onClick={this.add}>增加</button>
</div>
)
}
// 定义方法
sub = () => {
this.setState({
count: this.state.count - 1
})
}
add = () => {
this.setState({
count: this.state.count + 1
})
}
}
// 调用组件
ReactDOM.render(<Counter/>, document.getElementById('app'));
</script>
</body>
</html>
二、JSX 进阶
1.JSX注释
- 在JSX中遇到 <> 会当做XML元素解析, 遇到 { } 会当做JS解析
- 所以在JSX中不能使用HTML的注释
- JSX代码用于定义网页的结构, 而网页的内容中必然会包含内容
- 所以直接在JSX中使用单行注释//或多行注释/**/会被当做元素的内容处理
- 正确方式
- 告诉JSX, 注释的内容不是元素的内容即可
- 只需要将注释的内容写到 { } 中, JSX就会把注释的内容当做是JS来处理
-
{/* 注释 */}
2.JSX绑定内容
- 在JSX中只要看到 { } 就会当做JS解析(执行里面的JS代码)
- 所以无论是绑定属性,还是绑定类名,还是绑定样式, 只需要将字符串改为 {}
- 然后再通过JS动态获取, 动态绑定即可
- 2.1 绑定普通属性
- 过去怎么绑定, 现在就怎么绑定
-
我是段落
-
我是段落
-
- 过去怎么绑定, 现在就怎么绑定
- 2.2 绑定类名(class)
- 由于JSX本质是转换成JS代码, 而在JS中class有特殊含义, 所以不能使用
- 同理可证, 但凡是属性名称是JS关键字的都不能直接使用
-
className="active">绑定类名
- 2.3 绑定样式(style)
- 由于样式是键值对形式的, 所以在JSX中如果想要动态绑定样式
- 必须将样式放到一个对象中, 并且所有以-连接的样式名称都要转换成驼峰命名
-
{color:'red', fontSize:'50px'}}>绑定样式
-
// 定义结构(JSX)
render() {
return (
<div>
{/*1.对于普通属性而言, 过去怎么绑定, 现在就怎么绑定*/}
<p id="box">{this.state.message}</p>
<p title={this.state.message}>{this.state.message}</p>
{/*2.如果想通过JSX绑定类名, 那么不能直接通过class来绑定
JSX本质是转换成JS代码, 而在JS代码中class是一个关键字
所以不能直接使用class来绑定类名*/}
{/*3.所以以后但凡名称是JS关键字的属性, 都不能直接绑定*/}
<p className="active">{this.state.message}</p>
{/*4.如果想通过JSX绑定样式, 那么不能像过去一样编写
必须通过对象的形式来绑定*/}
{/*5.在绑定样式的时候, 如果过去在原生中是通过-连接的, 那么就必须转换成驼峰命名*/}
<p style={{ color: 'red', fontSize: '100px' }}>{this.state.message}</p>
</div>
)
}
3.JSX嵌入内容
- 1.JSX嵌入表达式
- 只要是合法的表达式, 都可以嵌入到JSX中
- 2.要想显示 true、false、undefined,必须先转换成 字符串
render(){
return (
<div>
{/*1.任何合法的JS表达式都可以嵌入到{}中*/}
<p>{this.state.flag ? '为真' : '为假'}</p>
{/*2.以下嵌入的内容不会被显示出来 [] true false null undefined*/}
<p>{[]}</p>
<p>{true}</p>
<p>{false}</p>
<p>{null}</p>
<p>{undefined}</p>
{/*3.如果想显示上面的这些内容, 那么就必须先转换成字符串
但是对于空数组来说, 哪怕转换成了字符串, 也不能显示*/}
<p>{[].toString()}</p>
<p>{true + ''}</p>
<p>{false + ''}</p>
<p>{null + ''}</p>
<p>{undefined + ''}</p>
{/*4.除了上述内容以外, 其它的内容都可以正常显示*/}
<p>我是段落</p>
<p>{this.state.message}</p>
</div>
)
}
4.JSX灵活性
- JSX使我们在JS中,拥有了直接编写XML代码的能力
- 所以在JS中能干的事,在JSX中都能干
需求:通过按钮,控制界面上p标签的显示和隐藏
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<!-- 引入react和react-dom -->
<script src='react17/react.development.v17.js'></script>
<script src='react17/react-dom.development.v17.js'></script>
<script src='react17/babel.min.js'></script>
</head>
<body>
<div id="app"></div>
<script type="text/babel">
// 需求:通过按钮控制界面上p标签的显示和隐藏
class Home extends React.Component{
constructor(){
super();
this.state = {
flag: true
}
}
render(){
return(
<div>
{/*和Vue中的v-show指令很像*/}
{/* <p style={{display: this.state.flag?'block':'none'}}>我是段落</p> */}
{/*和Vue中的v-if指令很像*/}
{/* &&表示当第一个参数取值为真时,才会执行第二个参数*/}
{this.state.flag && <p>我是段落</p>}
<button onClick={this.myFn}>按钮</button>
</div>
)
}
myFn = ()=>{
this.setState({
flag: !this.state.flag
})
}
}
ReactDOM.render(<Home/>, document.getElementById('app'));
</script>
</body>
</html>
需求:渲染列表元素
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<!-- 引入react和react-dom -->
<script src='react17/react.development.v17.js'></script>
<script src='react17/react-dom.development.v17.js'></script>
<script src='react17/babel.min.js'></script>
</head>
<body>
<div id="app"></div>
<script type="text/babel">
// 需求: 渲染列表元素
class Home extends React.Component {
constructor() {
super();
this.state = {
names: ['鲁班', '虞姬', '黄忠']
}
}
render() {
const { names } = this.state;
// const nameList = [];
// nameList.push(<li>{this.state.names[0]}</li>);
// nameList.push(<li>{this.state.names[1]}</li>);
// nameList.push(<li>{this.state.names[2]}</li>);
// nameList.push(<li>{this.state.names[3]}</li>);
// const nameList = [];
// for(let i = 0, j = names.length; i < j; i++){
// const li = <li>{names[i]}</li>
// nameList.push(li);
// }
// const nameList = names.map(name=>{
// return<li>{name}</li>
// });
return (
<div>
{/* 方式1 */}
{/*
<ul>
<li>{this.state.names[0]}</li>
<li>{this.state.names[1]}</li>
<li>{this.state.names[2]}</li>
</ul>
*/}
{/* 方式2 */}
{/*
<ul>{nameList}</ul>
*/}
{/* 方式3 */}
<ul>{
names.map(name => {
return <li>{name}</li>
})
}</ul>
</div>
)
}
}
ReactDOM.render(<Home />, document.getElementById('app'));
</script>
</body>
</html>
5.JSX书写规范
- JSX的顶层 只能有一个根元素
- JSX中的标签可以是单标签也可以是双标签, 但如果是单标签必须闭合( /> )
- 如果JSX中包含多个元素, 建议使用 () 包裹
6.JSX绑定事件
- JSX中绑定事件必须使用 驼峰命名
- onClick={this.btnClick}>按钮
- 事件监听方法中的 this
- 默认情况下React在调用事件监听方法的时候, 是通过apply来调用的
- 并且在调用的时候,将监听方法中的this修改为了undefined
- 所以默认情况下,我们是无法在监听方法中使用this的
- 监听方法this处理
- 1.箭头函数
- 2.通过添加监听方法的时候, 手动通过bind修改监听方法中的this
- 按钮
- 3.通过在构造函数中, 手动通过bind修改监听方法中的this
- this.myClick = this.btnClick.bind(this);
- 4.手动绑定一个箭头函数, 然后再箭头函数的函数体中手动调用监听方法 (企业推荐)
- () => { this.btnClick() }}>按钮
- 因为箭头函数中的this, 就是当前的实例对象
- 因为监听方法并不是React调用的, 而是我们在箭头函数中手动调用的
- 因为普通的方法, 默认情况下谁调用就是谁
class Home extends React.Component {
constructor() {
super();
this.state = {
message: '知播渔'
}
this.myClick = this.btnClick.bind(this);
}
render() {
return (
<div>
<div>{this.state.message}</div>
{/*方式2*/}
{/*
<button onClick={this.btnClick.bind(this)}>按钮</button>
*/}
{/*方式3*/}
{/*
<button onClick={this.myClick}>按钮</button>
*/}
{/*方式4: 优势就在于传参很方便*/}
<button onClick={() => { this.btnClick(1,2) }}>按钮</button>
</div>
)
}
btnClick(a,b) {
console.log(this);
console.log(a,b);
}
// 方式1:箭头函数
// btnClick =()=>{
// console.log(this);
// }
}
7.JSX事件对象
- 1.JSX事件参数
- 和原生JS一样, React在执行监听方法会传递一个事件对象给我们
- 但是React传递给我们的并不是原生的事件对象, 而是一个React自己合成的事件对象
- 2.什么是合成事件
- 合成事件是React在浏览器事件基础上做的一层包装,基本上有着和浏览器的原生事件有相同的接口,也能够进行 stopPropagation() 和 preventDefault(),并且合成事件在所有浏览器中的工作方式相同
- 如果由于某种原因需要浏览器的原生事件,则能够简单的通过nativeEvent属性就能够获取
render() {
return (
<div>
<div>{this.state.message}</div>
<button onClick={(e) => { console.log(e) }}>按钮</button>
<button onClick={(e) => { console.log(e.nativeEvent) }}>按钮</button>
</div>
)
}
三、组件核心
1.脚手架
- 1.什么是脚手架
- 脚手架是一种能快速帮助我们生成项目结构和依赖的工具
- 每个项目完成的效果不同,但是它们的基本工程化结构是相似的
- 既然相似,就没有必要每次都从零开始搭建,完全可以使用一些工具,帮助我们生成基本的项目模板
- 那么这个帮助我们生成项目模板的工具我们就称之为'脚手架'
- 2.什么是create-react-app
- create-react-app 就是 React的脚手架工具
- 它可以快速的帮我们生成一套利用webpack管理React的项目模板
2.利用脚手架构建react项目
- 1.如何安装和使用 create-react-app
- npm install -g create-react-app #安装脚手架工具
- create-react-app 项目名称 #创建react项目
- cd 项目名称 #进入项目
- npm start #启动项目
- 2.注意点
- 如果我们是通过create-react-app来创建React项目
- 那么在指定项目名称的时候, 项目的名称只能是英文, 并且只能是小写字母
- 如果出现了多个单词, 那么我们需要通过 _ 或者 - 来连接
- myName -> my_name 或 my-name
- 3.注意事项
- 第一次运行项目的时候大概率会出现一个错误, 会出现本地webpack的版本和项目 依赖的webpack版本不同的错误
- 如果遇到了这个错误, 我们就需要先通过 npm uninstall webapck 卸载掉本地的webpack
- 再通过 npm install -g webpack@xx.xx.xx 安装和项目相同版本的webpack即可
- 4.暴露 webapck 配置
- npm run eject
3.项目结构解读
项目名称
├── README.md // 说明文档
├── node_modules // 依赖包
├── package.json // 对应用程序的描述
├── .gitignore // 不通过git管理的文件描述
├── public
│ ├── favicon.ico // 应用程序收藏图标
│ ├── index.html // 应用程序入口
│ ├── logo192.png // manifest中PWA使用的图片
│ ├── logo512.png // manifest中PWA使用的图片
│ ├── manifest.json // PWA相关配置
│ └── robots.txt // 搜索引擎爬虫权限描述文件
└── src
├── App.css // 组件相关样式
├── App.js // 组件相关代码
├── App.test.js // 组件测试文件
├── index.css // 全局样式
├── index.js // 全局入口文件
├── logo.svg // React图标
└── serviceWorker.js // 注册PWA相关代码
4.组件的定义
- 1.什么是组件化开发
- 组件化开发其实就是分而治之的思想
- 我们可以将一个完整的界面拆分成多个小的界面, 每个小的界面就是一个组件
- 每个组件管理自己的状态(数据)和业务逻辑
- 这样做的既可以提高每个组件的复用性, 又可能降低每个组件的开发难度
- 2.如何定义组件
- 通过构造函数定义(函数组件, 无状态组件, 展示型组件)
- 通过类定义(类组件, 有状态组件, 逻辑组件)
如何在构建好的 react 项目中,编写代码?
src/App.js
import React from 'react';
import Header from './Components/Header'
import Main from './Components/Main'
import Footer from './Components/Footer'
// 定义App组件
class App extends React.Component{
render(){
return(
<div>
{/* 使用组件 */}
<Header />
<Main />
<Footer />
</div>
)
}
}
// 暴露App组件
export default App;
src/index.js
import ReactDOM from 'react-dom';
import React from 'react';
import App from './App';
// 渲染组件
ReactDOM.render(<App/>, document.getElementById('root'))
Components/Header.js (Compents - 专门用于定义组件)
import React from 'react';
// 导入样式
import './Header.css';
function Header() {
return (
<div className={'header'}>我是头部2</div>
)
}
export default Header;
Components/Header.css
.header{
background: red;
}
Components/Main.js
import React from 'react';
import './Main.css'
function Main() {
return (
<div className={'main'}>我是中间2</div>
)
}
export default Main;
Components/Main.css
.main{
background: green;
}
Components/Footer.js
import React from 'react';
import './Footer.css'
function Footer() {
return (
<div className={'footer'}>我是底部2</div>
)
}
export default Footer;
Components/Footer.css
.footer{
background: blue;
}
5.父子组件通讯
- 父组件 如何给 子组件 传递参数
- 父组件在调用子组件的时候,就可以给子组件传递数据
- 第一种:子组件为函数组件
- 1.如何在子组件设置参数默认值
- 通过 defaultProps
// 设置参数默认值
Header.defaultProps = {
name: '知播渔',
age: 666
}
- 2.如何在子组件中校验参数类型
- 通过 propTypes
- 首先需要安装prop-types: npm install prop-types
Header.propTypes = {
name: ReactTypes.string,
age: ReactTypes.number
}
- 第二种:子组件为类组件
- 改进:super(props);
6.子父组件通讯
- 子组件 如何给 父组件 传递参数
- 1.父组件传递一个方法给子组件
- 2.子组件在调用父组件传递过来的方法
- 3.只组件在调用这个方法的时候, 就可以通过方法传参的形式给父组件传递数据或者方法
7.跨组件通讯
- 可以一层一层的,进行数据传递
class Son extends React.Component{
// 3.子组件接收父组件传递过来的数据
constructor(props){
super(props);
}
render(){
return (
<div>
<p>我是儿子</p>
<p>{this.props.name}</p>
<button onClick={()=>{this.btnClick()}}>儿子按钮</button>
</div>
)
}
btnClick(){
// 儿子组件给爷爷组件传递数据:执行回调函数
this.props.appFn(18);
}
}
class Father extends React.Component{
// 爸爸组件接收数据
constructor(props){
super(props);
}
render(){
return (
<div>
<p>我是爸爸</p>
{/* 2.父组件给子组件继续传递数据 */}
<Son name={this.props.name} appFn={this.props.appFn}/>
</div>
)
}
}
class App extends React.Component{
render(){
return (
<div>
{/* 爷爷组件给子组件传递数据:一层一层的进行传递 */}
{/* 1.爷爷组件给父组件传递数据name */}
<Father name={'lnj'} appFn={this.myFn.bind(this)}/>
</div>
)
}
myFn(age){
console.log(age);
}
}
兄弟组件:
class A extends React.Component{
constructor(props){
super(props);
}
render(){
return (
<div>
<button onClick={()=>{this.btnClick()}}>A按钮</button>
</div>
)
}
btnClick(){
{/* 1.子组件:A给父组件传递数据 */ }
this.props.appFn('lnj');
}
}
class B extends React.Component{
// 接收父组件传递过来的数据
constructor(props){
super(props);
}
render(){
return (
<div>
<p>{this.props.name}</p>
</div>
)
}
}
class App extends React.Component{
constructor(props){
super(props);
this.state = {
name:''
}
}
render(){
return (
<div>
<A appFn={this.myFn.bind(this)}/>
{/* 2.父组件App给子组件B传递数据 */}
<B name={this.state.name}/>
</div>
)
}
myFn(name){
// console.log(name);
this.setState({
name: name
})
}
}
- 弊端:如果传递数据层次太深, 一层一层的传递比较麻烦, 所以React也提供了其它的解决方案
- 1.通过context上下文传递
- 2.通过Redux传递 (相当于Vuex)
- 3.通过Hooks传递 (相当牛X)
(1).如何通过 context 上下文来传递数据
- 方式1
- 1.调用创建上下文的方法, 只要我们调用了创建上下文的方法, 这个方法就会给我们返回 两个容器组件
- 生产者容器组件(Provider) / 消费者容器组件(Consumer)
- 2.只要拿到了这两个容器组件, 那么我们就可以通过这两个容器组件从祖先传递数据给 所有的后代了
- 3.首先在祖先组件中利用 '生产者容器组件' 包裹后代组件
- 4.然后在后代组件中利用 '消费者容器组件' 获取祖先组件传递过来的数据即可
- 1.调用创建上下文的方法, 只要我们调用了创建上下文的方法, 这个方法就会给我们返回 两个容器组件
import React from 'react';
// 1.创建一个上下文对象
const AppContext = React.createContext({});
// 2.从上下文对象中获取容器组件
const { Provider, Consumer } = AppContext;
class Son extends React.Component{
render(){
return(
// 4.通过Consumer消费者容器组件,消费数据
<Consumer>
{
(value)=>{
return(
<div>
<p>Son</p>
<p>{value.name}</p>
<p>{value.age}</p>
</div>
)
}
}
</Consumer>
)
}
}
class Father extends React.Component {
render() {
return (
<div>
<p>Father</p>
<Son/>
</div>
)
}
}
// 跨组件通讯:爷爷组件App如何给孙子组件Son传递参数?
// context上下文
// Provider: 生产者容器组件, 专门用于负责生产数据
// Consumer: 消费者容器组件, 专门用于消费生产者容器组件生产的数据的
// 容器组件 : 专门用于包裹其它组件的组件, 我们就称之为容器组件
class App extends React.Component {
render() {
return (
<div>
<p>App</p>
{/* 3.在Provider生产者容器组件中,通过value产生数据 */}
<Provider value={{name:'lnj', age:19}}>
<Father/>
</Provider>
</div>
)
}
}
export default App;
- 方式2
- 1.创建上下文对象并生产数据
- 2.指定当前组件的上下文
- 3.通过 context 消费数据
// 1.创建一个上下文对象,并生产数据
const AppContext = React.createContext({
name:'知播渔',
age: 666
});
class Son extends React.Component{
render(){
return (
<div>
{/*3.从当前组件的上下文中消费数据*/}
<p>{this.context.name}</p>
<p>{this.context.age}</p>
</div>
)
}
}
// 2.指定当前组件的上下文
Son.contextType = AppContext;
class Father extends React.Component{
render(){
return (
<div>
<p>{this.context.name}</p>
<p>{this.context.age}</p>
<Son></Son>
</div>
)
}
}
Father.contextType = AppContext;
class App extends React.Component{
render(){
return (
<div>
<Father></Father>
</div>
)
}
}
- 注意:可以有多个生产者容器与消费者容器,但是如果有多个,则不能使用第二种方式
- context 方式的弊端
- 1.通过context我们已经能够实现跨组件通讯,但是只能实现从上往下传递
- 不能实现从下往上传递或者同级之间传递
- 子父组件之间通讯, 是通过回调函数的方式
- 兄弟组件之间通讯, 也是通过父组件, 通过回调函数的方式
- 2.但是如果通过回调函数, 传统的方式我们需要一层一层的传递, 比较复杂
- 所以我们可以借助一个第三方库(events)来实现跨组件事件通讯
(2).如何使用 events 库实现跨组件通讯
- 1.安装events库:npm install events
- 2.创建EventEmitter对象:eventBus对象;
- 3.监听事件:eventBus.addListener("事件名称", 监听函数);
- 4.移除事件:eventBus.removeListener("事件名称", 监听函数);
- 5.触发事件:eventBus.emit("事件名称", 参数列表);
import React from 'react';
import { EventEmitter } from 'events';
// 跨组件通讯:孙子组件Son如何给爷爷组件App传递数据?
// events
// 监听事件:eventBus.addListener("事件名称", 监听函数) ;
// 移除事件:eventBus.removeListener("事件名称", 监听函数) ;
// 触发事件:eventBus.emit("事件名称", 参数列表);
// 1.在全局创建一个全局的事件管理器对象
const eventBus = new EventEmitter();
class Son extends React.Component{
render(){
return(
<div>
<p>Son</p>
<button onClick={()=>{this.btnClick()}}>按钮</button>
</div>
)
}
btnClick(){
// 4.触发事件,并通过参数给爷爷组件App传递数据
eventBus.emit('say', 'lnj', 18);
}
}
class Father extends React.Component {
render() {
return (
<div>
<p>Father</p>
<Son/>
</div>
)
}
}
class App extends React.Component {
// 2.监听事件
// componentDidMount:当组件被渲染到界面时,React会自动调用该方法
componentDidMount(){
eventBus.addListener('say',this.appFn.bin(this));
}
// 3.移除事件
// componentWillUnmount:当组件被卸载时,React会自动调用该方法
componentWillUnmount(){
eventBus.removeListener('say', this.appFn.bin(this));
}
// 5.爷爷组件接收数据
appFn(name, age){
console.log(name, age);
}
render() {
return (
<div>
<p>App</p>
<Father/>
</div>
)
}
}
export default App;
state 与 setState:
- 1.props 和 state 的区别
- props 和 state 都是用来存储数据的
- props:存储的是父组件传递过来的数据(props只读)
- state:存储的是自己的数据(state可读可写)
- props 和 state 都是用来存储数据的
- 2.setState是同步的还是异步的
- 在组件生命周期或React合成事件中,setState是异步;(异步是为了优化性能)
- 在setTimeout或者原生dom事件中,setState是同步;
- 3.如何拿到更新之后的数据
- setState方法其实可以接收两个参数
- 通过setState方法的第二个参数, 通过回调函数拿到
this.setState({
age: 111
}, ()=>{
console.log('回调函数中', this.state.age);
});
- 4.setState 是如何给 state 赋值的
- 通过 Object.assign()
let oldObj = {name:'lnj', age:18};
let newObj = {age: 666};
/*
{name:'lnj', age:666}
* */
let obj = Object.assign({}, oldObj, newObj);
console.log(obj);
- 5.State合并现象
- 因为 setState 会收集一段时间内所有的修改再更新界面
- 所以就出现了 State 合并现象
- 如何解决?
this.setState({
age: this.state.age + 1
}, ()=>{
this.setState({
age: this.state.age + 1
}, ()=>{
this.setState({
age: this.state.age + 1
});
});
});
*/
this.setState((preState, props)=>{
return {age: preState.age + 1}
})
this.setState((preState, props)=>{
return {age: preState.age + 1}
})
this.setState((preState, props)=>{
return {age: preState.age + 1}
})
四、深入组件
1.组件的生命周期
- 1.什么是生命周期
- 事物从生到死的过程, 我们称之为生命周期
- 2.什么是生命周期方法
- 事物在从生到死过程中, 在特定时间节点调用的方法, 我们称之为生命周期方法
- 3.React组件生命周期方法
- 组件从生到死的过程, 在在特定的时间节点调用的方法, 我们称之为组件的生命周期方法
- constructor函数:组件被创建的时候, 就会调用
- render函数:渲染组件的时候, 就会调用
- componentDidMount函数:组件已经挂载到DOM上时(渲染已经完成),就会回调
- componentDidUpdate函数:组件已经发生了更新时(重新渲染已完成),就会回调
- componentWillUnmount函数:组件即将被移除时,就会回调
- 组件从生到死的过程, 在在特定的时间节点调用的方法, 我们称之为组件的生命周期方法
2.组件的生命周期方法
常用的组件生命周期方法:
- 1. constructor 生命周期方法中做什么
- 通过 props 接收父组件传递过来的数据
- 通过 this.state 初始化内部的数据
- 通过 bind 为事件绑定实例(this)
- this.myClick = this.btnClick.bind(this);
- 2. render 生命周期方法中做什么
- 返回组件的网页结构
- 3. componentDidMount 生命周期方法中做什么
- 依赖于DOM的操作可以在这里进行
- 在此处发送网络请求就最好的地方(官方建议)
- 可以在此处添加一些订阅(会在componentWillUnmount取消订阅)
- 4. componentDidUpdate 生命周期方法中做什么
- 可以在此对更新之后的组件进行操作
- 5. componentWillUnmount 生命周期方法中做什么
- 在此方法中执行必要的清理操作;例如,清除timer定时器,取消网络请求或清除
- 在 componentDidMount() 中创建的订阅等
不常用的组件生命周期方法:
- 1. getDerivedStateFromProps:将props中的数据映射到state中
- 2. shouldComponentUpdate:更新时,决定是否更新(true更新 flase不更新)
- 3. getSnapshotBeforeUpdate:获取更新之前的数据
3.组件的渲染流程与更新流程
(1).React 渲染流程
- 1.执行render方法
<div>
<div><p>我是段落</p></div>
<div><span>我是span</span></div>
</div>
- 2.将JSX转换成createElement
React.createElement("div", null,
React.createElement("div", null,
React.createElement("p", null, "我是段落")),
React.createElement("div", null,
React.createElement("span", null, "我是span"))
);
- 3.执行createElement创建虚拟DOM, 得到虚拟DOM树
{
targetName: 'div',
children:[
{
targetName: 'div',
children:[
{
targetName: 'p'
}
]
},
{
targetName: 'div',
children:[
{
targetName: 'span'
}
]
}
]
}
- 4.根据虚拟DOM树在界面上生成真实DOM
(2).React 更新流程
- 1.props/state发生改变
- 2.render方法重新执行
- 3.将JSX转换成createElement
- 4.利用createElement重新生成新的虚拟DOM树
- 5.新旧虚拟DOM通过diff算法进行比较
- 6.每发现一个不同就生成一个mutation
- 7.根据mutation更新真实DOM
4.React-Diff 算法
- 只会比较同层元素
- 只会比较同位置元素(默认)
- 在比较过程中
- 同类型元素做修改
- 不同类型元素重新创建
5.列表渲染优化
- 1.列表渲染优化
- 由于diff算法在比较的时候,默认情况下只会进行同层同位置的比较
- 所以在渲染列表时可能会存在性能问题
- 2.如何让diff算法,递归比较同层所有元素
- 给列表元素添加 key ,告诉React除了和同层同位置比, 还需要和同层其它位置比
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<!-- 引入react和react-dom -->
<script src='react17/react.development.v17.js'></script>
<script src='react17/react-dom.development.v17.js'></script>
<script src='react17/babel.min.js'></script>
</head>
<body>
<div id="app"></div>
<script type="text/babel">
// 需求: 渲染列表元素
class Home extends React.Component {
constructor(props) {
super(props);
this.state = {
heroList: ['鲁班', '虞姬', '黄忠']
}
}
// 1.列表渲染优化
// 由于diff算法在比较的时候默认情况下只会进行同层同位置的比较
// 所以在渲染列表时可能会存在性能问题
// 2.如何让diff算法递归比较同层所有元素
// 给列表元素添加key, 告诉React除了和同层同位置比, 还需要和同层其它位置比
render() {
return (
<div>
<ul>{
this.state.heroList.map(name => {
// key={name}:提升性能
return <li key={name}>{name}</li>
})
}</ul>
<button onClick={() => { this.btnClick() }}>按钮</button>
</div>
)
}
btnClick() {
this.setState({
// heroList: [...this.state.heroList, '阿珂']
heroList: ['阿珂', ...this.state.heroList]
})
}
}
ReactDOM.render(<Home />, document.getElementById('app'));
</script>
</body>
</html>
- key注意点
- 如果列表中出现相同的key, 所以我们必须保证列表中key的取值唯一
6.组件性能优化
- 1.嵌套组件的render调用
- 默认情况下, 只要父组件render被调用, 那么所有后代组件的render也会被调用
- 2.当前存在的问题
- 如果我们只修改了父组件的数据, 并没有修改子组件的数据, 并且子组件中也没有用到 父组件中的数据
- 那么子组件还是会重新渲染, 子组件的render方法还是会重新执行, 这样就带来了性能问题
- 3.解决方案
- 3.1 类组件
- 方式1
- 在子组件中,添加 shouldComponentUpdate 方法,判断自身的 state 数据是否 发生改变
- 3.1 类组件
class Home extends React.Component{
constructor(props){
super(props);
this.state = {
age : 18
}
}
shouldComponentUpdate(nextProps, nextState, nextContext) {
// return true;
// return false;
if(this.state.age !== nextState.age){
return true;
}
return false;
}
render(){
console.log('Home-render被调用');
return (
<div>
<p>{this.state.age}</p>
</div>
)
}
}
class App extends React.Component{
constructor(props){
super(props);
this.state = {
name : 'lnj'
}
}
render(){
console.log('App-render被调用');
return (
<div>
<p>{this.state.name}</p>
<button onClick={()=>{this.btnClick()}}>APP按钮</button>
<Home/>
</div>
)
}
btnClick(){
this.setState({
name:'知播渔'
})
}
}
- 存在的问题(shouldComponentUpdate)
- 所有需要优化子组件都需要实现这个方法, 但这个方法并没有技术含量
- 方式2
- 让类组件 继承 于 React.PureComponent , 让React自动帮我们实现
- 3.2 函数组件
- 对于函数组件来说
- 没有继承关系
- 没有生命周期方法
- 对于类组件来说
- 我们可以通过实现shouldComponentUpdate方法
- 或者继承于PureComponent来解决性能优化问题,
- 但是对于函数式组件, 是没有生命周期的,是没有继承关系的
- 那么在函数式组件中如何解决性能优化问题呢
- 通过 React.memo() 高阶函数
- React.memo() 会返回一个优化后的组件给我们
- 通过 React.memo() 高阶函数
- 对于函数组件来说
/*
function Home() {
console.log('Home-render被调用');
return (
<div>
<p>Home</p>
</div>
)
}
*/
const PurHome = React.memo(function() {
console.log('Home-render被调用');
return (
<div>
<p>Home</p>
</div>
)
});
- state 注意点
- 永远不要直接修改state中的数据
- 如果要修改state中的数据, 必须通过 setState 传递一个新的值
7.React 获取元素
(1).React 获取元素的几种方式
- 1.传统方式(在React中及其不推荐)
- let oP = document.getElementById('box');
- 2.字符串(即将被废弃, 也不推荐)
- let oP = this.refs.box;
- 3.对象(推荐)
- let oP = this.oPRef.current;
- 4.回调函数(推荐)
- let oP = this.oPRef;
import React from 'react';
class App extends React.PureComponent {
constructor(props) {
super(props);
// this.oPRef = React.createRef();
this.oPRef = null;
}
render() {
console.log('App-render被调用');
return (
<div>
{/* <p id={'box'}>我是段落</p> */}
{/* <p ref={'box'}>我是段落</p> */}
{/* <p ref={this.oPRef}>我是段落</p> */}
<p ref={(arg) => { this.oPRef = arg }}>我是段落</p>
<button onClick={() => { this.btnClick() }}>APP按钮</button>
</div>
)
}
btnClick() {
// 1.传统方式(在React中及其不推荐)
// let oP = document.getElementById('box');
// 2.通过ref='字符串' / this.refs.字符串 (通过字符串的方式即将被废弃, 也不推荐)
// let oP = this.refs.box;
// 3.通过createRef()创建一个对象, 然后将这个对象传递给ref (推荐)
// let oP = this.oPRef.current;
// 4.通过传递一个回调函数, 然后保存回调函数参数的方式(推荐)
let oP = this.oPRef;
oP.innerHTML = 'www.it666.com';
console.log(oP);
}
}
export default App;
- 注意点
- 获取原生元素, 拿到的是元素本身
- 获取类组件元素, 拿到的是组件实例对象
- 获取函数组件元素, 拿不到任何内容
import React from 'react';
// 类组件
class Home extends React.PureComponent {
render() {
return (
<div>Home</div>
)
}
}
// 函数组件
function About() {
return (
<div>About</div>
)
}
class App extends React.PureComponent {
constructor(props) {
super(props);
this.myRef = React.createRef();
}
render() {
return (
<div>
{/* <p ref={this.myRef}>我是段落</p> */}
{/* <Home ref={this.myRef}/> */}
<About ref={this.myRef} />
<button onClick={() => { this.btnClick() }}>APP按钮</button>
</div>
)
}
btnClick() {
// 1.如果获取的是原生的元素, 那么拿到的就是元素本身
// 2.如果获取的是类组件元素, 那么拿到的就是类组件的实例对象
// 3.如果获取的是函数组件元素, 那么什么都拿不到
console.log(this.myRef.current);
}
}
export default App;
(2).如何获取函数组件中的元素
- 如果要获取的不是函数式组件本身,而是想获取函数式组件中的某个元素
- 那么我们可以使用 Ref转发 来获取 ( React.forwardRef() )
import React from 'react';
// 函数组件
// function About() {
// return (
// <div>About</div>
// )
// }
// Ref转发
const About = React.forwardRef(function(props, myRef){
return(
<div>
<p>我是段落</p>
<span ref={myRef}>我是Span</span>
</div>
)
});
class App extends React.PureComponent {
constructor(props) {
super(props);
this.myRef = React.createRef();
}
render() {
return (
<div>
<About ref={this.myRef} />
<button onClick={() => { this.btnClick() }}>APP按钮</button>
</div>
)
}
btnClick() {
console.log(this.myRef.current);
}
}
export default App;
8.受控组件
- 1.什么是受控组件
- 值 受到 react 控制的 表单元素(如:input、textarea 和 select
)
- 数据来源 与 数据去向 都是 state
- 官方文档:https://reactjs.bootcss.com/docs/forms.html
- 值 受到 react 控制的 表单元素(如:input、textarea 和 select
- 2.如何定义受控组件
- value:设置默认初始值
- onChange:获取用户输入
class App extends React.PureComponent{
constructor(props){
super(props);
this.state = {
name: 'lnj'
}
}
render(){
return (
<form>
<input type="text"
value={this.state.name}
onChange={(e)=>{this.change(e)}}/>
</form>
)
}
change(e){
console.log(e.target.value);
this.setState({
name: e.target.value
})
}
}
- 3.优化受控组件
- 添加 name 属性
- 使用 e.target.name 获取 state 的 key(name email phone)
class App extends React.PureComponent{
constructor(props){
super(props);
this.state = {
name: 'lnj',
email: '97606813@qq.com',
phone: '13554499311'
}
}
render(){
return (
<form>
<input type="text"
value={this.state.name}
name={'name'}
onChange={(e)=>{this.change(e)}}/>
<input type="text"
name={'email'}
value={this.state.email}
onChange={(e)=>{this.change(e)}}/>
<input type="text"
name={'phone'}
value={this.state.phone}
onChange={(e)=>{this.change(e)}}/>
</form>
)
}
/*
nameChange(e){
console.log(e.target.name);
this.setState({
name: e.target.value
})
}
emailChange(e){
console.log(e.target.name);
this.setState({
email: e.target.value
})
}
phoneChange(e){
console.log(e.target.name);
this.setState({
phone: e.target.value
})
}
*/
change(e){
console.log(e.target.name);
this.setState({
[e.target.name]: e.target.value
})
}
}
- 非受控组件:默认状态下的表单元素都是非受控组件
9.高阶组件
- 什么是高阶组件
- Higher-Order Components,简称为 HOC
- 参数为组件,返回值为新组件 的函数
class Home extends React.PureComponent{
render(){
return (
<div>Home</div>
)
}
}
// 定义高阶组件
function enhanceComponent(WrappedComponent) {
// 定义组件
class AdvComponent extends React.PureComponent{
render() {
return (
<div>
// 调用传递过来的参数(组件)
<WrappedComponent/>
</div>
)
}
}
// 返回组件
return AdvComponent;
}
// 调用高阶组件
const AdvComponent = enhanceComponent(Home);
class App extends React.PureComponent{
constructor(props){
super(props);
}
render(){
return (
<div>
// 使用高阶组件
<AdvComponent/>
</div>
)
}
}
- 高阶组件的应用场景
-
代码复用 / 增强Props / 抽离State / 生命周期拦截
-
权限控制
-
抽离State :( 跨组件通讯 context )
const UserContext = React.createContext({});
const {Provider, Consumer} = UserContext;
/*
class Father1 extends React.PureComponent{
render() {
return (
<div>
<Son1/>
</div>
)
}
}
class Father2 extends React.PureComponent{
render() {
return (
<div>
<Son2/>
</div>
)
}
}
class Son1 extends React.PureComponent{
render() {
return (
// Consumer:消费数据
<Consumer>{
value =>{
return (
<div>
<p>{value.name}</p>
<p>{value.age}</p>
</div>
)
}
}</Consumer>
)
}
}
class Son2 extends React.PureComponent{
render() {
return (
// Consumer:消费数据
<Consumer>{
value =>{
return (
<ul>
<li>{value.name}</li>
<li>{value.age}</li>
</ul>
)
}
}</Consumer>
)
}
}
*/
// 优化:
class Son1 extends React.PureComponent{
render() {
return (
<div>
<p>{this.props.name}</p>
<p>{this.props.age}</p>
</div>
)
}
}
class Son2 extends React.PureComponent{
render() {
return (
<ul>
<li>{this.props.name}</li>
<li>{this.props.age}</li>
</ul>
)
}
}
// 定义高阶函数
function EnhancedComponent (WrappedComponent) {
class Father extends React.PureComponent{
render() {
return (
<Consumer>{
value =>{
return (
/*
<WrappedComponent name={value.name} age={value.age}/>
*/
<WrappedComponent {...value} {...this.props}/>
)
}
}</Consumer>
)
}
}
return Father;
}
const Father1 = EnhancedComponent(Son1);
const Father2 = EnhancedComponent(Son2);
class App extends React.PureComponent{
constructor(props){
super(props);
}
render(){
return (
// Provider:生产数据
<Provider value={{name:'zs', age:18}}>
<Father1/>
<Father2/>
</Provider>
)
}
}
生命周期拦截:( 跨组件通讯 events)
import React from 'react';
import {EventEmitter} from 'events';
const UserContext = React.createContext({});
const {Provider, Consumer} = UserContext;
const eventBus = new EventEmitter();
class Son1 extends React.PureComponent{
/*
constructor(props){
super(props);
this.state = {
list: []
}
}
componentDidMount() {
eventBus.addListener('update', this.update.bind(this));
}
componentWillUnmount() {
eventBus.removeListener('update', this.update.bind(this));
}
update(list){
this.setState({
list: list
})
}
*/
render() {
return (
<div>
<p>{this.props.name}</p>
<p>{this.props.age}</p>
<p>{this.props.country}</p>
{
this.props.list.map(name=>{
return <p key={name}>{name}</p>
})
}
</div>
)
}
}
class Son2 extends React.PureComponent{
/*
constructor(props){
super(props);
this.state = {
list: []
}
}
componentDidMount() {
eventBus.addListener('update', this.update.bind(this));
}
componentWillUnmount() {
eventBus.removeListener('update', this.update.bind(this));
}
update(list){
this.setState({
list: list
})
}
*/
render() {
return (
<ul>
<li>{this.props.name}</li>
<li>{this.props.age}</li>
<li>{this.props.country}</li>
{
this.props.list.map(name=>{
return <li key={name}>{name}</li>
})
}
</ul>
)
}
}
// 定义高阶函数
function EnhancedComponent (WrappedComponent) {
class Father extends React.PureComponent{
constructor(props){
super(props);
this.state = {
list: []
}
}
componentDidMount() {
eventBus.addListener('update', this.update.bind(this));
}
componentWillUnmount() {
eventBus.removeListener('update', this.update.bind(this));
}
update(list){
this.setState({
list: list
})
}
render() {
return (
<Consumer>{
value =>{
return (
<WrappedComponent {...value} {...this.props} {...this.state}/>
)
}
}</Consumer>
)
}
}
return Father;
}
const Father1 = EnhancedComponent(Son1);
const Father2 = EnhancedComponent(Son2);
class App extends React.PureComponent{
constructor(props){
super(props);
}
render(){
return (
<Provider value={{name:'zs', age:18}}>
<Father1 country={'中国'}/>
<Father2 country={'俄罗斯'}/>
<button onClick={()=>{this.btnClick()}}>按钮</button>
</Provider>
)
}
btnClick(){
eventBus.emit('update', ['鲁班', '虞姬', '黄忠']);
}
}
export default App;
权限控制:
class Info extends React.PureComponent{
render() {
return (
<div>用户信息</div>
)
}
}
class Login extends React.PureComponent{
render() {
return (
<div>用户登录</div>
)
}
}
function EnhancedComponent (WrappedComponent) {
class Authority extends React.PureComponent{
render() {
if(this.props.isLogin){
return <Info/>
}else{
return <Login/>
}
}
}
return Authority;
}
const AuthorityInfo = EnhancedComponent(Info);
class App extends React.PureComponent{
constructor(props){
super(props);
}
render(){
return (
<AuthorityInfo isLogin={true}/>
)
}
}
10.Portal
- 默认情况下, 所以的组件都是渲染到 root 元素中的
- Portal 提供了一种将组件渲染到其它元素中的能力
import React from 'react';
import ReactDOM from 'react-dom';
class Modal extends React.PureComponent{
render() {
/*
this.props.children: 可以获取到当前组件所有的子元素或者子组件
createPortal: 接收两个参数
第一个参数: 需要渲染的内容
第二个参数: 渲染到什么地方
* */
return ReactDOM.createPortal(this.props.children, document.getElementById('other'));
}
}
class App extends React.PureComponent{
constructor(props){
super(props);
}
render(){
return (
<div id={'app'}>
<Modal>
{/* 定义子元素 */}
<div id={'modal'}>Modal</div>
</Modal>
</div>
)
}
}
export default App;
11.Fragment
- 由于React规定, 组件中只能有一个根元素
- 所以每次编写组件的时候, 我们都需要在最外层包裹一个冗余的标签
- 如果不想渲染这个冗余的标签, 那么就可以使用 Fragment 来代替
代码冗余:
如何改进:
class Home extends React.PureComponent{
constructor(props){
super(props);
this.state = {
heroList: ['鲁班', '虞姬', '黄忠']
}
}
render() {
// 如果组件的结构比较复杂, 那么只能有一个根元素
return (
/*
<React.Fragment>
<p>{this.state.heroList[0]}</p>
<p>{this.state.heroList[1]}</p>
<p>{this.state.heroList[2]}</p>
</React.Fragment>
*/
// 如果需要给Fragment添加key, 那么就不能使用语法糖
<>
{
this.state.heroList.map(name=>{
return (
<React.Fragment key={name}>
{/* <p key={name}>{name}</p> */}
<p>{name}</p>
</React.Fragment>
)
})
}
</>
)
}
}
class App extends React.PureComponent{
constructor(props){
super(props);
}
render(){
return (
/*
<React.Fragment>
<Home/>
</React.Fragment>
*/
// 下面的这种写法就是上面写法的语法糖
<>
<Home/>
</>
)
}
}
12.StrictMode
- 1.什么是 StrictMode
- 作用
- 开启严格模式, 检查后代组件中是否存在潜在问题
- 注意点
- 和 Fragment 一样, 不会渲染出任何UI元素
- 仅在'开发模式'下有效
- 作用
- 2.StrictMode 检查什么
- 检查过时或废弃的属性/方法/...
- 检查意外的副作用
- 这个组件的 constructor 会被调用两次
- 检查这里写的一些逻辑代码被调用多次时,是否会产生一些副作用
ReactDOM.render(
<React.StrictMode>
<App/>
</React.StrictMode>
, document.getElementById('root'));
五、React样式
- React中的样式
- React并没有像Vue那样提供特定的区域给我们编写CSS代码
- 所以你会发现在React代码中, CSS样式的写法千奇百怪
1.内联样式
- 内联样式的优点
- 内联样式, 样式之间不会有冲突
- 可以动态获取当前state中的状态
- 内联样式的缺点
- 写法上都需要使用驼峰标识
- 某些样式没有提示
- 大量的样式, 代码混乱
- 某些样式无法编写(比如伪类/伪元素)
import React from 'react';
class App extends React.Component{
constructor(props){
super(props);
this.state = {
color: 'red'
}
}
render(){
return(
<div>
{/* 1.内联样式 */}
<p style={{fontSize: '20px', color: this.state.color}}>我是段落1</p>
<p style={{fontsize: '20px', color: 'green'}}>我是段落2</p>
<button onClick={()=>{this.btnClick()}}>按钮</button>
</div>
)
}
btnClick(){
this.setState({
color: 'blue'
})
}
}
export default App;
2.外链样式
- 将CSS代码写到一个单独的CSS文件中, 在使用的时候导入进来
- 外链样式的优点
- 编写简单, 有代码提示, 支持所有CSS语法
- 外链样式的缺点
- 不可以动态获取当前state中的状态
- 属于全局的css,样式之间会相互影响(可以设置id避免相互影响)
components/Home.js
import React from 'react';
// 2.外链式
// 导入样式
import './Home.css';
class Home extends React.Component{
render(){
return(
<div id='home'>
<p>我是home段落</p>
<a href="www.it66.com">我是home超链接</a>
</div>
)
}
}
export default Home;
components/Home.css
3.Css Module
- React的脚手架,已经内置了css modules的配置
- .css/.less/.scss 等样式文件都修改成 .module.css/.module.less/.module.scss 等;
- Css Modules优点
- 编写简单, 有代码提示, 支持所有CSS语法
- 解决了全局样式相互污染问题
- Css Modules缺点
- 不可以动态获取当前 state 中的状态
components/About.js
import React from 'react';
// 导入样式
import AboutStyle from './About.module.css';
class About extends React.Component{
render(){
return(
<div id='home'>
{/* 3.Css Module */}
<p className={AboutStyle.title}>我是about段落</p>
<a href="www.it66.com" className={AboutStyle.link}>我是about超链接</a>
</div>
)
}
}
export default About;
components/About.moudule.css
- 模板字符串
- 在JS中除了可以通过()来调用函数以外,其实我们还可以通过模板字符串来调用函数
- 通过模板字符串调用函数规律
- 参数列表中的第一个参数是一个数组, 这个数组中保存了所有不是插入的值
- 参数列表的第二个参数开始, 保存的就是所有插入的值
const name = 'lnj';
const age = 18;
/*
const str = `my name is ${name}, my age is ${age}`;
console.log(str); // my name is lnj, my age is 18
*/
function test(...args) {
console.log(args);
}
// 在JS中除了可以通过()来调用函数以外,
// 其实我们还可以通过模板字符串来调用函数
// test(1, 3, 5); // [ 1, 3, 5 ]
// test`1, 3, 5`; // [ [ '1, 3, 5' ] ]
test`1, 3, 5, ${name}, ${age}`; // [ [ '1, 3, 5, ', ', ', '' ], 'lnj', 18 ]
- 总结
- 我们可以拿到模板字符串中所有的内容
- 我们可以拿到模板字符串中所有非插入的内容
- 我们可以拿到模板字符串中所有插入的内容
- 所以我们就可以对模板字符串中所有的内容进行单独的处理
4.CSS-in-JS
- 1.CSS-in-JS
- React认为结构和逻辑是密不可分的,所以在React中结构代码也是通过JS来编写的
- 正是受到React这种思想的影响, 所以就有很多人开发了用JS来编写CSS的库
- styled-components / emotion
- 利用JS来编写CSS, 可以让CSS具备样式嵌套,函数定义,逻辑复用,动态修改状态等特性
- 也就是说, 从某种层面上, 提供了比过去Less/Scss更为强大的功能
- 所以Css-in-JS, 在React中也是一种比较推荐的方式
- 2.styled-components 使用
- 1.安装 styled-components
- npm install styled-components --save
- 2.导入 styled-components
- import styled from 'styled-components';
- 3.利用 styled-components 创建组件并设置样式
- 注意:vscode 需要安装 vscode-styled-components 插件,才能有代码提示
- 1.安装 styled-components
import React from 'react';
import styled from 'styled-components';
// 4.CSS-in-JS
const StyleDiv = styled.div`
p{
font-size: 20px;
color: red;
}
a{
font-size: 25px;
color: green;
}
`
class Top extends React.Component{
render(){
return(
<StyleDiv>
<p>我是top段落</p>
<a href={'www.it666.com'}>我是top超链接</a>
</StyleDiv>
)
}
}
export default Top;
- 3.styled-components 特性
- props
- attrs
props特性:
attrs特性:
import React from 'react';
import styled from 'styled-components';
const StyleDiv = styled.div`
p{
font-size: 20px;
/* 1.props特性 */
color: ${props => props.color};
}
a{
font-size: 25px;
color: green;
}
`
// 2.attrs特性
// 注意点:调用完attrs方法之后, 这个方法返回的还是一个函数
// 所以我们还可以继续通过字符串模板来调用
const StyleInput = styled.input.attrs({
// 密文input输入框
type: 'password'
})``
class Bottom extends React.Component{
constructor(props){
super(props);
this.state = {
color: 'pink'
}
}
render(){
return(
// 组件传参,参数会保存到props中
<StyleDiv color={this.state.color}>
<p>我是bottom段落</p>
<a href={'www.it666.com'}>我是bottom超链接</a>
<button onClick={()=>{this.btnClick()}}>按钮</button>
{/* <StyleInput type={'password'}></StyleInput> */}
<StyleInput></StyleInput>
</StyleDiv>
)
}
btnClick(){
this.setState({
color: 'blue'
})
}
}
export default Bottom;
- 4.如何通过 styled-components 设置主题(通用样式设置,比如:字体、大小、颜色等)
-
ThemeProvider
-
App.js
components/About.js
components/Home.js
- 5.styled-components 继承
import React from 'react';
import './App.css'
import styled from 'styled-components'
/*
const StyleDiv1 = styled.div`
font-size: 100px;
color: red;
background: blue;
`;
const StyleDiv2 = styled.div`
font-size: 100px;
color: green;
background: blue;
`;
*/
const BaseDiv = styled.div`
font-size: 100px;
background: blue;
`;
// styled-components 继承
const StyleDiv1 = styled(BaseDiv)`
color: red;
`;
const StyleDiv2 = styled(BaseDiv)`
color: green;
`;
class App extends React.Component{
render(){
return (
<div>
<StyleDiv1>我是div1</StyleDiv1>
<StyleDiv2>我是div2</StyleDiv2>
</div>
);
}
}
export default App;
六、React 动画
- 1.React 过渡动画
- 在React中我们可以通过原生的CSS来实现过渡动画,
- 但是React社区为我们提供了 react-transition-group 帮助我们快速过渡动画
- 2.动画组件
- Transition
- 该组件是一个和平台无关的组件(不一定要结合CSS);
- 在前端开发中,我们一般是结合CSS来完成样式,所以比较常用的是CSSTransition;
- CSSTransition
- 在前端开发中,通常使用CSSTransition来完成过渡动画效果
- SwitchTransition
- 两个组件显示和隐藏切换时,使用该组件
- TransitionGroup
- 将多个动画组件包裹在其中,一般用于列表中元素的动画;
- Transition
原生动画
const StyleDiv = styled.div`
width: ${props => props.width};
height: ${props => props.height};
background: skyblue;
opacity: ${props => props.opacity};
transition: all 3s;
`
class App extends React.Component{
constructor(props){
super(props);
this.state = {
width: 0,
height: 0,
opacity:0
}
}
render(){
return (
<div>
<StyleDiv {...this.state}></StyleDiv>
<button onClick={()=>{this.btnClick()}}>按钮</button>
</div>
);
}
btnClick(){
this.setState({
width: '100px',
height: '100px',
opacity:1
})
}
}
- CSSTransition状态
- CSSTransition有三个状态
- appear: 初始
- enter : 进入
- exit: 退出
- 当组件第一次加载时候会自动查找
- appear / -appear-active / -appear-done
- 当组件显示时会自动查找
- enter / -enter-active / -enter-done
- 当组件退出时会自动查找
- exit / -exit-active / -exit-done
- CSSTransition有三个状态
- 如何通过 CSSTransition 来实现过渡效果?
- 1.1 安装react-transition-group
- npm install react-transition-group --save
- 1.2 从安装好的库中导入CSSTransition
- import {CSSTransition} from 'react-transition-group';
- 1.3 利用CSSTransition将需要执行过渡效果的组件或元素包裹起来
- 1.4 编写对应的CSS动画
- 实现: .-enter / .-enter-active / .-enter-done
- 1.5 给CSSTransition添加一些属性
- in属性
- 取值是一个布尔值, 如果取值为false表示触发退出动画, 如果取值是true表示 触发进入动画
- classNames属性
- 指定动画类名的前缀
- timeout属性
- 设置动画超时时间
- 1.1 安装react-transition-group
App.js
import React from 'react';
import './App.css'
import {CSSTransition} from 'react-transition-group';
class App extends React.Component{
constructor(props){
super(props);
this.state = {
isShow: false
}
}
render(){
return (
<div>
<CSSTransition in={this.state.isShow}
classNames={'box'}
timeout={3000}>
<div></div>
</CSSTransition>
<button onClick={()=>{this.btnClick()}}>显示</button>
</div>
);
}
btnClick(){
this.setState({
isShow: true
})
}
}
export default App;
App.css
.box-enter{
/*进入动画执行之前绑定的类名*/
width: 0;
height: 0;
opacity: 0;
background: skyblue;
}
.box-enter-active{
/*进入动画执行过程中绑定的类名*/
width: 100px;
height: 100px;
opacity: 1;
transition: all 3s;
}
.box-enter-done{
/*进入动画执行完毕之后绑定的类名*/
width: 100px;
height: 100px;
opacity: 1;
background: red;
}
七、React 路由
1.什么是路由
- 路由维护了URL地址和组件的映射关系, 通过这个映射关系,
- 我们就可以根据不同的URL地址,去渲染不同的组件
2.如何使用路由
- 2.1.安装 react-router-dom
- npm install react-router-dom
- 2.2.通过指定监听模式
- BrowserRouter: history模式 http://www.it666.com/home
- HashRouter: hash模式 http://www.it666.com/#/home
- 2.3.通过 Link 修改路由URL地址
- 2.4.通过 Route 匹配路由地址
import {BrowserRouter, HashRouter, Link, Route} from 'react-router-dom';
class App extends React.PureComponent{
render(){
// 需求: 界面上有两个按钮, 点击不同按钮显示不同组件
return (
<div>
{/*
1.指定路由的监听模式
*/}
<HashRouter>
{/*
2.修改URL的资源地址
*/}
<Link to={'/home'}>Home</Link>
<Link to={'/about'}>About</Link>
{/*
3.维护URL和组件的关系
*/}
<Route path={'/home'} component={Home}/>
<Route path={'/about'} component={About}/>
</HashRouter>
</div>
)
}
}
3.React 路由注意点
- react-router4之前, 所有路由代码都是统一放到react-router中管理的
- react-router4开始, 拆分为了两个包react-router-dom和react-router-native
- react-router-dom 在浏览器中使用路由;react-router-native 在原生应用中使用路由
- BrowserRouter history模式使用的是H5的特性, 所以兼容性会比HashRouter hash模式差一些
- 在企业开发中如果不需要兼容低级版本浏览器, 建议使用 BrowserRouter
- 如果需要兼容低级版本浏览器, 那么只能使用 HashRouter
- 无论是Link还是Route,都只能放到BrowserRouter和HashRouter中才有效
- 3.1 Route 注意点
- 默认情况下Route在匹配资源地址时, 是模糊匹配
- 如果必须和资源地址一模一样才匹配, 那么需要添加 exact 属性, 开启精准匹配
-
<Route exact path={'/home'} component={Home}/> <Route exact path={'/home/about'} component={About}/>
- 3.2 Link注意点
- 默认情况下Link会渲染成一个 a标签, 如果想渲染成其他的元素, 可以通过手动路由 跳转来实现
- 3.3 NavLink注意点
- 默认情况下NavLink在匹配资源地址时, 是模糊匹配
- 如果必须和资源地址一模一样才匹配, 那么需要添加 exact 属性, 开启精准匹配
- NavLink有一个 activeStyle 属性,用于指定样式
-
<NavLink exact to={'/home'} activeStyle={{color:'red'}}>Home</NavLink> <NavLink exact to={'/home/about'} activeStyle={{color:'red'}}>About</NavLink>
4. Switch 仅匹配一个
- 默认情况下,路由会从上至下匹配所有的Route, 只要匹配都会显示
- 但是在企业开发中,大部分情况下, 我们希望的是一旦有一个匹配到了后续就不要再匹配
- 此时我们就可以通过 Switch 来实现
-
<Switch> <Route exact path={'/home'} component={Home}/> <Route exact path={'/about'} component={About}/> {/* 如果Route没有指定path, 那么表示这个Route和所有的资源地址都匹配 */} <Route component={Other}/> </Switch>
5. Redirect 重定向
- 资源重定向, 也就是可以在访问某个资源地址的时候,重定向到另外一个资源地址
- 例如: 访问 /user 用户界面, 如果未登录,则重定向到 /login 登录界面
6.嵌套路由
- 路由里面又有路由, 我们就称之为嵌套路由
-
如果要使用嵌套路由, 那么外层路由不能添加精准匹配 exact
-
子路由不用使用 BrowserRouter 或 HashRouter 来包裹 NavLink/Switch/Route
App.js
import React from 'react';
import Discover from './components/Discover'
import { BrowserRouter, NavLink, Route, Switch } from 'react-router-dom';
/*
1.嵌套路由(子路由):
路由里面又有路由, 我们就称之为嵌套路由
*/
class App extends React.PureComponent {
render() {
return (
<div>
<BrowserRouter>
<NavLink to={'/discover'} activeStyle={{ color: 'red' }}>广场</NavLink>
<Switch>
{/* 注意:如果要使用嵌套路由, 那么外层路由不能添加精准匹配 exact */}
<Route path={'/discover'} component={Discover} />
</Switch>
</BrowserRouter>
</div>
)
}
}
export default App;
components/Discover.js
import React from 'react';
import { NavLink, Switch, Route } from "react-router-dom";
// 定义函数组件
function Hot() {
return (
<div>推荐</div>
)
}
function TopList() {
return (
<div>排行榜</div>
)
}
function PlayList() {
return (
<div>歌单</div>
)
}
// 定义类组件
class Discover extends React.PureComponent{
render(){
return(
// 注意点: 由于当前组件是在 BrowserRouter 或 HashRouter 中显示的
// 所以在当前组件中不用使用 BrowserRouter 或 HashRouter 来包裹 NavLink/Switch/Route
<div>
{/* 之所以指定为/discover,是将其设置为二级路由的默认显示界面 */}
<NavLink exact to={'/discover'} activeStyle={{ color: 'blue' }}>推荐</NavLink>
<NavLink exact to={'/discover/toplist'} activeStyle={{ color: 'blue' }}>排行榜</NavLink>
<NavLink exact to={'/discover/playlist'} activeStyle={{ color: 'blue' }}>歌单</NavLink>
<Switch>
<Route exact path={'/discover'} component={Hot} />
<Route exact path={'/discover/toplist'} component={TopList} />
<Route exact path={'/discover/playlist'} component={PlayList} />
</Switch>
</div>
)
}
}
export default Discover;
7.手动路由跳转
- 不通过Link/NavLink来设置资源地址, 而是通过JS来设置资源地址
- 手动指定路由
- { this.btnClick() }}>歌单
-
Hash模式:window.location.hash = 'URL地址';
-
子路由:this.props.history.push('URL地址');(组件是由路由创建出来的)
- 手动指定路由
class Discover extends React.PureComponent{
render(){
return(
// 注意点: 由于当前组件是在 BrowserRouter 或 HashRouter 中显示的
// 所以在当前组件中不用使用 BrowserRouter 或 HashRouter 来包裹 NavLink/Switch/Route
<div>
{/* 之所以指定为/discover,是将其设置为二级路由的默认显示界面 */}
<NavLink exact to={'/discover'} activeStyle={{ color: 'blue' }}>推荐</NavLink>
<NavLink exact to={'/discover/toplist'} activeStyle={{ color: 'blue' }}>排行榜</NavLink>
<NavLink exact to={'/discover/playlist'} activeStyle={{ color: 'blue' }}>歌单</NavLink>
{/* 手动路由跳转 */}
<button onClick={() => { this.btnClick() }}>歌单</button>
<Switch>
<Route exact path={'/discover'} component={Hot} />
<Route exact path={'/discover/toplist'} component={TopList} />
<Route exact path={'/discover/playlist'} component={PlayList} />
</Switch>
</div>
)
}
btnClick(){
// 1.如果是Hash模式, 那么只需要通过JS设置Hash值即可
// window.location.hash = '/discover/playlist';
// 如果一个组件是通过路由创建出来的, 那么系统就会自动传递一个history给我们
// 我们只需要拿到这个history对象, 调用这个对象的push方法, 通过push方法修改资源地址即可
// console.log(this.props.history);
// 2.通过history对象的push方法
this.props.history.push('/discover/playlist');
}
}
- 注意点:
- 只有通过路由创建出来的组件才有history对象, 所以不能在根组件中使用手动路由跳转
- 如果想在根组件中使用手动路由跳转, 那么需要借助一个withRouter高阶组件
import React from 'react';
import Discover from './components/Discover'
import { BrowserRouter, HashRouter, NavLink, Route, Switch, withRouter} from 'react-router-dom';
class App extends React.PureComponent {
render() {
return (
<div>
{/* <HashRouter> */}
<NavLink to={'/discover'} activeStyle={{ color: 'red' }}>广场</NavLink>
{/* 手动路由跳转 */}
<button onClick={() => { this.btnClick() }}>广场</button>
<Switch>
<Route path={'/discover'} component={Discover} />
</Switch>
{/* </HashRouter> */}
</div>
)
}
btnClick() {
// 1.Hash模式, 通过设置Hash值即可
window.location.hash = '/discover';
/*
如果一个组件是通过路由创建的, 那么系统就会自动给这个组件传递一个history对象
但是如果一个组件不是通过路由创建的, 那么系统就不会给这个组件传递一个history对象
如果现在非路由创建出来的组件中使用history对象, 那么可以借助withRouter高阶组件
只要把一个组件传递给withRouter方法, 那么这个方法就会通过路由将传入的组件创建出来
注意点: 如果一个组件要使用路由创建, 那么这个组件必须包裹在 BrowserRouter, HashRouter中
* */
// 2.通过history对象的push方法(还需借助withRouter高阶组件)
this.props.history.push('/discover');
}
}
export default withRouter(App);
8.路由传参
- 1.URL参数
- ?key=value&key=value
- 2.路由参数(动态路由)
- /path/:key
- 3.对象
- state 即为要传递的数据
{
pathname: "/user",
search: "",
hash: "",
state: obj
}
App.js
class App extends React.PureComponent {
render() {
let obj = {
name: 'lnj',
age: 18,
gender: 'man'
};
return (
<div>
<HashRouter>
{/* 1.URL参数 */}
<NavLink to={'/home?name=lnj&age=18'} activeStyle={{ color: 'red' }}>Home</NavLink>
<NavLink to={'/about/lnj/age'} activeStyle={{ color: 'red' }}>About</NavLink>
{/* 3.对象 */}
<NavLink to={{
pathname: "/user",
search: "",
hash: "",
state: obj
}} activeStyle={{ color: 'red' }}>User</NavLink>
<Switch>
<Route path={'/home'} component={Home} />
{/* 2.路由参数(动态路由) */}
<Route path={'/about/:name/:age'} component={About} />
<Route path={'/user'} component={User} />
</Switch>
</HashRouter>
</div>
)
}
}
components/Home.js(URL参数)
class About extends React.PureComponent{
constructor(props){
super(props);
// console.log(this.props.location);
// console.log(this.props.location.search);
// substring:从第一个字符开始截取
let query = this.props.location.search.substring(1);
query = query.split('&');
// ["name=lnj", "age=18"]
// console.log(query);
let obj = {};
query.forEach((item) => {
// name=lnj age=18
let temp = item.split('=');
// ["name", "lnj"] ["age", 18]
// console.log(temp);
obj[temp[0]] = temp[1];
});
console.log(obj);
}
render(){
return (
<div>Home</div>
)
}
}
- this.props.match.params (路由参数)
- this.props.location.state (对象)
9.路由集中管理
- 现在虽然我们能通过路由实现组件切换, 但是现在我们的路由都比较分散,
- 不利于我们管理和维护
- 所以React也考虑到了这个问题, 也给我们提供了统一管理路由的方案
- 1.安装 react-router-config
- npm install react-router-config
- 2.在src目录下,新建router文件
- 3.编写 index.js文件
- 4.使用 routers
- {renderRoutes(routers)} (一级路由)
src/router/index.js:
import Home from '../components/Home'
import About from '../components/About'
import User from '../components/User'
const routers=[
{
// 访问/home地址时,调用Home组件
path: '/home', // 路由地址
exact: true, // 开启精准匹配
component: Home // 匹配组件
},
{
path: '/about/:name/:age',
exact: true,
component: About
},
{
path: '/user',
exact: true,
component: User
},
]
export default routers;
- 二级路由如何定义
- routes: [ ]
-
{renderRoutes(this.props.route.routes)} (二级路由)