FrontEndNotes笔记 原课程链接 大佬仓库 React 中文官网
React:用于构建用户界面的 JavaScript 库。由 Facebook 开发且开源。是一个将视图渲染为html视图的开源库
原生 JavaScript 的痛点:
判断this指向
class类
ES6语法规范
npm包管理
原型以及原型链
数组常用方法
模块化
相关js库
<!-- 准备好一个“容器” -->
<div id="test"></div>
<!-- 引入react核心库 -->
<script type="text/javascript" src="../js/react.development.js"></script>
<!-- 引入react-dom,用于支持react操作DOM -->
<script type="text/javascript" src="../js/react-dom.development.js"></script>
<!-- 引入babel,用于将jsx转为js -->
<script type="text/javascript" src="../js/babel.min.js"></script>
<!-- 此处一定要写babel,表示写的不是 JS,而是 JSX,并且靠 babel 翻译 -->
<script type="text/babel">
//1.创建虚拟DOM 此处是JSX 不要写引号,因为不是字符串
const VDOM = <h1>Hello,React</h1>
//2.渲染虚拟DOM到页面
// 导入核心库和扩展库后,会有 React 和 ReactDOM 两个对象
ReactDOM.render(VDOM, document.getElementById('test'))
</script>
第一种 jsx方式(推荐)
<div id="test"></div>
<script type="text/javascript" src="../js/react.development.js"></script>
<script type="text/javascript" src="../js/react-dom.development.js"></script>
<script type="text/javascript" src="../js/babel.min.js"></script>
<script type="text/babel">
//1.创建虚拟DOM
const VDOM = (
<h1>
<span>Hello,React</span>
</h1>
)
//2.渲染虚拟DOM到页面
ReactDOM.render(VDOM, document.getElementById('test'))
</script>
第二种方式
<div id="test"></div>
<script type="text/javascript" src="../js/react.development.js"></script>
<script type="text/javascript" src="../js/react-dom.development.js"></script>
<script type="text/javascript">
//1.创建虚拟DOM
const VDOM = React.createElement('h1',{id:'title'},React.createElement('span',{},'Hell,React'))
//2.渲染虚拟DOM到页面
ReactDOM.render(VDOM, document.getElementById('test'))
</script>
关于虚拟 DOM:
<script type="text/babel">
//1.创建虚拟DOM
const VDOM = (
<h1>
<span>Hello,React</span>
</h1>
)
//2.渲染虚拟DOM到页面
ReactDOM.render(VDOM, document.getElementById('test'))
console.log('VDOM',VDOM); // VDOM {...}
console.log('TDOM',document.querySelect('#test')); // TDOM <div>...</div>
console.log(typeof VDOM); // object
console.log(VDOM instanceof Object); // true
</script>
全称:JavaScript XML React 定义的类似于 XML 的 JS 扩展语法;本质是 React.createElement() 方法的语法糖 作用:简化创建虚拟 DOM 补充:js中,JSON的序列化和反序列化使用parse()/stringify() JSX 语法规则
<input type="text" />
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>jsx语法规则</title>
<style>
.title {
background-color: orange;
width: 200px;
}
</style>
</head>
<body>
<div id="test"></div>
...
<script type="text/babel">
const myId = 'aTgUiGu'
const myData = 'HeLlo,rEaCt'
const VDOM = (
<div>
<h2 className="title" id={myId.toLowerCase()}>
<span style={{ color: 'white', fontSize: '19px' }}>{myData.toLowerCase()}</span>
</h2>
<input type="text" />
// <good>very good</good>
// <Child></Child>
</div>
)
ReactDOM.render(VDOM, document.getElementById('test'))
</script>
</body>
</html>
补充:表达式与语句(代码)的区别
a
a+b
demo(1)
arr.map()
function test () {}
if(){}
for(){}
switch(){case:xxxx}
<div id="app"></div>
<script type="text/javascript" src="../js/react.development.js"></script>
<script type="text/javascript" src="../js/react-dom.development.js"></script>
<script type="text/javascript" src="../js/babel.min.js"></script>
<script type="text/babel">
// 创建组件
function Com() {
console.log(this) //babel编译后开启严格模式 此处的this是undefind
return <h1>函数式组件,适用于简单组建的创建</h1>
}
// 挂载组件
ReactDOM.render(<Com/>,document.querySelector("#app"))
</script>
要点:
渲染组件的过程:
类的基本知识
<script type="text/javascript">
/*
总结:
1. 类的实例不是必须写的,需要对类进行初始化操作,如添加指定属性时才写
2. 子类继承父类,子类中写了构造器,那么子类构造器中的super是必须要调用的
3. 类中定义的方法都是放在了类的原型对象上,供实例去使用
4. 实例调用子类的方法时,找不到会去父类的原型对象上去找,一直找到顶级window对象
*/
class Person {
// 构造器方法
constructor(name,age) {
// 构造器中的this指向类的实例对象(constructor是一个函数,由实例对象调用,所以this指向实例对象)
this.name = name
this.age = age
}
// 一般方法
speak() {
// 放在了类的原型对象上,供实例使用
// 通过Person实例调用speak方法时,speak中的this指向Person实例
console.log(`我的名字叫${this.name},今年${this.age}岁了`);
}
}
// Student类继承与Person类
class Student extends Person {
constructor(name,age,id) {
super(name,age)
this.id = id
this.school = "bilibili"
}
// 重写从父类继承的方法
speak() {
console.log(`我的名字叫${this.name},今年${this.age}岁了,编号为${this.id}`);
}
}
const p1 = new Person('张三',18)
p1.speak()
const s1 = new Student('李四',16,'30461')
s1.speak()
</script>
组件渲染过程:
state 是组件实例对象最重要的属性,值为对象。又称为状态机,通过更新组件的 state 来更新对应的页面显示。 要点:
<script type="text/babel">
class Weather extends React.Component {
constructor(props) {
super(props)
// 初始化状态
this.state = {isHot:true}
// 解决changeWeather中的this指向问题
this.change = this.changeWeather.bind(this)
}
render() {
// 读取状态
const {isHot} = this.state
// 这里的this指向原型对象
// return <h1 onClick={this.changeWeather}>今天天气{isHot?'凉爽':'炎热'}</h1>
// 这里的this指向实例自身
return <h1 onClick={this.change}>今天天气{isHot ? '1' :'2' }</h1>
}
changeWeather() {
// changeWeather放在实例对象的原型上,供实例使用
// changeWeather作为onClick的回调函数使用,不是通过实例调用,是直接调用
// 类中的方法默认开启了严格模式,this指向丢失
// 状态不可直接更改,必须借助React内置API
// this.state.isHot = !this.state.isHot
this.setState({isHot:!this.state.isHot})
}
}
function demo() {
// 修改状态
}
ReactDOM.render(<Weather/>,document.querySelector('#app'))
</script>
<script type="text/babel">
// 创建组件
class Weather extends React.Component {
// 初始化状态
state = {isHot:true}
render() {
// 读取状态
const {isHot} = this.state
return <h1 onClick={this.changeWeather}>今天天气{isHot ? '炎热' :'凉爽' }</h1>
}
// 自定义方法 -- 赋值语句+箭头函数(中的this指向上下文中的this)
changeWeather = ()=>{
this.setState({isHot:!this.state.isHot})
}
}
ReactDOM.render(<Weather/>,document.querySelector('#app'))
</script>
每个组件对象都有 props 属性,组件标签的属性都保存在 props 中。(注意:props 是只读的,不能修改。) 要点:
<script type="text/babel">
// 假数据
const obj = {name:"张三",age:18,sex:"男"}
// 创建组件
class Person extends React.Component {
render() {
const {name,age,sex} = this.props
return (
<div>
<h1>{name}</h1>
<h2>{age}</h2>
<h2>{sex}</h2>
</div>
)
}
}
// 渲染组件
// ReactDOM.render(<Person name="zhangsan" age="18" sex="1"/>,document.querySelector('#app'))
// 批量传递props/批量传递标签属性
ReactDOM.render(<Person {...obj}/>,document.querySelector('#app'))
</script>
<div id="app1"></div>
<div id="app2"></div>
<div id="app3"></div>
<script type="text/javascript" src="../js/react.development.js"></script>
<script type="text/javascript" src="../js/react-dom.development.js"></script>
<script type="text/javascript" src="../js/babel.min.js"></script>
<!-- 引入prop-type 用于限制props属性标签 -->
<script type="text/javascript" src="../js/prop-types.js"></script>
<script type="text/babel">
// 假数据
const obj = {name:"张三",age:18,sex:"男"}
// 创建组件
class Person extends React.Component {
render() {
const {name,age,sex} = this.props
return (
<div>
<span>{name},</span>
<span>{age},</span>
<span>{sex}</span>
</div>
)
}
}
// 对标签属性进行限制
Person.propTypes = {
name:PropTypes.string.isRequired,//限制name必传且为String类型
age:PropTypes.number,//限制age为number类型
sex:PropTypes.string,//限制sex为String类型
speak:PropTypes.func//限制speak为函数类型
}
Person.defaultProps = {
age:18,//指定age默认值
sex:'保密'//指定sex默认值
}
function speak() {
console.log('hello');
}
// 渲染组件
ReactDOM.render(<Person name="李四" age={20} sex="男" speak={speak}/>,document.querySelector('#app1'))
ReactDOM.render(<Person name="李五"/>,document.querySelector('#app3'))
// 批量传递props/批量传递标签属性
ReactDOM.render(<Person {...obj}/>,document.querySelector('#app2'))
</script>
<script type="text/babel">
// 假数据
const obj = {name:"张三",age:18,sex:"男"}
// 创建组件
function Person(props) {
const {name,age,sex} = props
return (
<div>
<h1>{name}</h1>
<h2>{age}</h2>
<h2>{sex}</h2>
</div>
)
}
// 对标签属性进行限制
Person.propTypes = {
name:PropTypes.string.isRequired,//限制name必传且为String类型
age:PropTypes.number,//限制age为number类型
sex:PropTypes.string,//限制sex为String类型
speak:PropTypes.func//限制speak为函数类型
}
Person.defaultProps = {
age:18,//指定age默认值
sex:'保密'//指定sex默认值
}
ReactDOM.render(<Person {...obj}/>,document.querySelector('#app2'))
</script>
<!-- 引入prop-type 用于限制props属性标签 -->
<script type="text/javascript" src="../js/prop-types.js"></script>
<script type="text/babel">
// 假数据
const obj = {name:"张三",age:18,sex:"男"}
// 创建组件
class Person extends React.Component {
render() {
const {name,age,sex} = this.props
return (
<div>
<span>{name}</span>
<span>{age}</span>
<span>{sex}</span>
</div>
)
}
// 对标签属性进行限制
static propTypes = {
name:PropTypes.string.isRequired,//限制name必传且为String类型
age:PropTypes.number,//限制age为number类型
sex:PropTypes.string,//限制sex为String类型
speak:PropTypes.func//限制speak为函数类型
}
static defaultProps = {
age:18,//指定age默认值
sex:'保密'//指定sex默认值
}
}
function speak() {
console.log('hello');
}
// 渲染组件
ReactDOM.render(<Person name="李四" age={20} sex="男" speak={speak}/>,document.querySelector('#app1'))
ReactDOM.render(<Person name="李五"/>,document.querySelector('#app3'))
// 批量传递props/批量传递标签属性
ReactDOM.render(<Person {...obj}/>,document.querySelector('#app2'))
</script>
通过定义 ref 属性可以给标签添加标识。
<script type="text/babel">
class Demo extends React.Component {
showData = ()=>{
const {input1,innput2} = this.refs
console.log(input1);
}
render() {
return (
<div>
<input ref="input1" type="text"/>
<br/>
<br/>
<button onClick={this.showData}>点击确认文字</button>
<input ref="input2" type="text"/>
</div>
)
}
}
ReactDOM.render(<Demo/>,document.querySelector('#app'))
</script>
<script type="text/babel">
class Demo extends React.Component {
showData = ()=>{
const {input1,innput2} = this
console.log(input1);
}
render() {
return (
<div>
<input ref={(currentNode) => {this.input1=currentNode}} type="text"/>
<br/>
<br/>
<button onClick={this.showData}>点击确认文字</button>
<input ref="input2" type="text"/>
</div>
)
}
}
ReactDOM.render(<Demo/>,document.querySelector('#app'))
</script>
<script type="text/babel">
class Demo extends React.Component {
myRef = React.createRef()
showData = ()=>{
console.log(this.myRef);//{current: input}
alert(this.myRef.current.value)
}
render() {
return (
<div>
<input ref={this.myRef} type="text"/>
<br/>
<br/>
<button onClick={this.showData}>点击确认文字</button>
</div>
)
}
}
ReactDOM.render(<Demo/>,document.querySelector('#app'))
</script>
class Demo extends React.Component {
showData2 = (event) => {
alert(event.target.value)
}
render() {
return (
<div>
<input onBlur={this.showData2} type="text" placeholder="失去焦点提示数据" />
</div>
)
}
}
非受控组件:现用现取。即需要使用时,再获取节点得到数据 受控组件:类似于 Vue 双向绑定的从视图层绑定到数据层(推荐使用,因为非受控组件需要使用大量的 ref 。)
<script type="text/babel">
class Demo extends React.Component {
state = {
username:'1',
password:'2'
}
saveUsername = (event)=>{
this.setState({username:event.target.value})
}
savePassword = (event)=>{
this.setState({password:event.target.value})
}
handleSubmit = (event) => {
event.preventDefault() //阻止表单默认提交
const {username,password} = this.state
alert(`${username},${password}`)
}
render() {
return (
<form action="" onSubmit={this.handleSubmit}>
用户名:<input onChange = {this.saveUsername} type="text"/>
密码: <input onChange = {this.savePassword} type="password"/>
<button>登录</button>
</form>
)
}
}
ReactDOM.render(<Demo/>,document.querySelector('#app'))
</script>
<script type="text/babel">
class Demo extends React.Component {
handleSubmit = (event) => {
event.preventDefault() //阻止表单默认提交
const {username,password} = this
alert(`${username.value},${password.value}`)
}
render() {
return (
<form action="" onSubmit={this.handleSubmit}>
用户名:<input ref={c => this.username = c} type="text"/>
密码: <input ref={c => this.password = c} type="password"/>
<button>登录</button>
</form>
)
}
}
ReactDOM.render(<Demo/>,document.querySelector('#app'))
</script>
高阶函数:参数为函数或者返回一个函数的函数,常见的如 Promise、setTimeout、Array.map()等 函数柯里化:通过函数调用继续返回函数的方式,实现多次接收参数最后统一处理的函数编码形式 使用高阶函数简化受控组件:
<script type="text/babel">
class Demo extends React.Component {
// 初始化state
state = {
username:'1',
password:'2'
}
// 自定义事件
saveFormData = (dateType)=>{
return (event) => {
this.setState({[dateType]:event.target.value})
}
}
handleSubmit = (event) => {
event.preventDefault() //阻止表单默认提交
const {username,password} = this.state
alert(`${username},${password}`)
}
render() {
return (
<form action="" onSubmit={this.handleSubmit}>
用户名:<input onChange = {this.saveFormData('username')} type="text"/>
密码: <input onChange = {this.saveFormData('password')} type="password"/>
<button>登录</button>
</form>
)
}
}
// 渲染组件
ReactDOM.render(<Demo/>,document.querySelector('#app'))
</script>
理解
1.3.1 初始化阶段
1.3.2 更新阶段 【第一种情况】父组件重新render触发
1.3.3 卸载组件
<script type="text/babel">
class Demo extends React.Component {
state = {newsArr:[]}
componentDidMount() {
setInterval(() => {
// 获取状态
let {newsArr} = this.state
const news = '列表'+(newsArr.length+1)
// 更新状态
this.setState({newsArr:[news,...newsArr]})
}, 1000);
}
getSnapshotBeforeUpdate() {
return this.refs.list.scrollHeight
}
componentDidUpdate(preProps,preState,height) {
this.refs.list.scrollTop += this.refs.list.scrollHeight - height
}
render() {
return (
<div>
<ul className="list" ref="list">
{
this.state.newsArr.map((n,index) => {
return <li key={index} className="news">{n}</li>
})
}
</ul>
</div>
)
}
}
ReactDOM.render(<Demo/>,document.querySelector("#app"))
</script>
a. 旧虚拟DOM中找到了与新虚拟DOM相同的key:
(1).若虚拟DOM中内容没变, 直接使用之前的真实DOM
(2).若虚拟DOM中内容变了, 则生成新的真实DOM,随后替换掉页面中之前的真实DOM
b. 旧虚拟DOM中未找到与新虚拟DOM相同的key
根据数据创建新的真实DOM,随后渲染到到页面
!https://img-blog.csdnimg.cn/2021060821262976.png 2. 用index作为key可能会引发的问题
注意!如果不存在对数据的逆序添加、逆序删除等破坏顺序操作,仅用于渲染列表用于展示,使用index作为key是没有问题的
全局安装配置 :npm i -g create-react-app
创建项目:create-react-app 项目名
C:\Users\Mrnianj>npm i -g create-react-app
...
C:\Users\Mrnianj>create-reacrt-app 项目名
...
C:\Users\Mrnianj>cd 项目(文件夹)名
...
C:\Users\Mrnianj>npm start
脚手架目录:
public ---- 静态资源文件夹
favicon.icon ------ 网站页签图标
index.html -------- 主页面
logo192.png ------- logo图
logo512.png ------- logo图
manifest.json ----- 应用加壳的配置文件
robots.txt -------- 爬虫协议文件
src ---- 源码文件夹
App.css -------- App组件的样式
App.js --------- App组件
App.test.js ---- 用于给App做测试
index.css ------ 样式
index.js ------- 入口文件
logo.svg ------- logo图
reportWebVitals.js
--- 页面性能分析文件(需要web-vitals库的支持)
setupTests.js
---- 组件单元测试的文件(需要jest-dom库的支持)
public/index.html
<body>
<div id="root"></div>
</body>
src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
);
src/app.js
// 创建外壳组件
import React,{Component} from "react";
import Hello from "./components/Hello/Hello"
import Welcome from "./components/Welcome"
// 暴露并创建组件
export default class App extends Component {
render() {
return (
<div>
<Hello/>
<Welcome/>
</div>
)
}
}
src/components/Hello
import React ,{ Component } from "react";
export default class Hello extends Component{
render() {
return (
<h1>Hello React!!!</h1>
)
}
}
src/components/Welcome
import React,{Component} from "react"
export default class Welcome extends Component {
render() {
return <h1>Welcome</h1>
}
}
TodoList案例
// 父组件
class Father extends Component {
state: {
todos: [{ id: '001', name: '吃饭', done: true }],
flag: true,
}
addTodo = (todo) => {
const { todos } = this.state
const newTodos = [todo, ...todos]
this.setState({ todos: newTodos })
}
render() {
return <List todos={this.state.todos} addTodo={this.addTodo} />
}
}
// 子组件
class Son extends Component {
// 由于 addTodo 是箭头函数,this 指向父组件实例对象,因此子组件调用它相当于父组件实例在调用
handleClick = () => {
this.props.addTodo({ id: '002', name: '敲代码', done: false })
}
render() {
return <button onClick={this.handleClick}>添加</button>
}
}
安装 axios npm i axios
export default class App extends Component {
getStuData = ()=> {
axios.get('http://localhost:3000/api1/students').then(
response => {console.log(response.data);},
err => {console.log(err);}
)
}
getCarData = ()=> {
axios.get('http://localhost:3000/api2/cars').then(
response => {console.log(response.data);},
err => {console.log(err);}
)
}
render() {
return (
<div>
<button onClick={this.getStuData}>获取学生</button>
<button onClick={this.getCarData}>获取汽车</button>
</div>
)
}
}
官方文档
方法1:
优点:配置简单,前端请求资源可不加前缀
缺点:不能配置多个代理
工作方式:当请求了 3000 端口号(本机)不存在的资源时,就会把请求转发给 5000 端口号服务器
在 package.json 文件中进行配置:"proxy": "http://localhost:5000"
方法2:
在 src 目录下创建代理配置文件 setupProxy.js ,进行配置:
// const proxy = require('http-proxy-middleware')
const { createProxyMiddleware } = require('http-proxy-middleware')
module.exports = function (app) {
app.use(
//api1是需要转发的请求(所有带有/api1前缀的请求都会转发给5000)
createProxyMiddleware('/api1', {
//配置转发目标地址(能返回数据的服务器地址)
target: 'http://localhost:5000',
//控制服务器接收到的请求头中host字段的值
/*
changeOrigin设置为true时,服务器收到的请求头中的host为:localhost:5000
changeOrigin设置为false时,服务器收到的请求头中的host为:localhost:3000
changeOrigin默认值为false,但一般将changeOrigin改为true
*/
changeOrigin: true,
//去除请求前缀,保证交给后台服务器的是正常请求地址(必须配置)
pathRewrite: { '^/api1': '' },
}),
createProxyMiddleware('/api2', {
target: 'http://localhost:5001',
changeOrigin: true,
pathRewrite: { '^/api2': '' },
})
)
}
React 中兄弟组件或任意组件之间的通信方式。 使用的工具库:pubsub-js
npm install pubsub-js --save
// A组件
import PubSub from 'pubsub-js'
// 组件被挂载后订阅消息
componentDidMount() {
this.token = PubSub.subscribe('topic', (msg,data)=>{//msg 订阅消息名,data 传递的数据
console.log('List组件收到订阅消息',data);
this.setState(data)
})
}
// 组件将被卸载时取消订阅
componentWillUnmount() {
PubSub.unsubscribe(this.token)
}
// B组件
// 发布消息
import PubSub from 'pubsub-js'
export default class Search extends Component {
saveUsers = ()=>{
// 发布消息
// 发送网络请求后 通知A组件更新状态
PubSub.publish('topic', {isLoading:true,isFirst:false})
axios.get(`https://api.github.com/search/users?q=username`).then(
(res)=>{
PubSub.publish('topic', {isLoading:false,users:res.data.items})
},
(err)=>{
PubSub.publish('topic', {isLoading:false,err})
}
)
}
}
阮一峰 Fetch API 教程 常见可以发送ajax请求的方式
// 使用fetch发送请求
fetch(url)
.then(response => response.json())
.then(data => console.log(data))
.catch(e => console.log("获取数据失败", e))
// 使用 await 语法优化
async function getJSON() {
let url = 'https://api.github.com/users/ruanyf';
try {
let response = await fetch(url);
return await response.json();
} catch (error) {
console.log('请求出错', error);
}
}
let obj = { a: { b: 1 } }
//传统解构赋值
const { a } = obj
//连续解构赋值
const {
a: { b },
} = obj
//连续解构赋值 + 重命名
const {
a: { b: value },
} = obj
try {
// 先看服务器是否联系得上
const response = await fetch(`/api1/search/users2?q=${keyWord}`)
// 再获取数据
const data = await response.json()
console.log(data)
} catch (error) {
console.log('请求出错', error)
}
router.get(path, function(req, res))
<Route path="/test" component={Test}>
window.history
BOM 浏览器对象中包含 history 对象用于管理浏览器历史记录,History 对象是 window 对象的一部分,可通过 window.history 属性对其进行访问。
方法 说明
back() 加载 history 列表中的前一个 URL
forward() 加载 history 列表中的下一个 URL
go() 加载 history 列表中的某个具体页面
react的一个插件库,用来实现一个SPA应用。 react-router web应用官方文档
// 安装 5.X 版本路由
npm install react-router-dom@5.2.0 -S
是
// 最新已经 6.X 版本,用法和 5.X 有所不同
npm install react-router-dom -S
6.x 版本的基本使用参考 2. 编写基本路由 index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import { BrowserRouter} from "react-router-dom";
ReactDOM.render(
<BrowserRouter>
<App/>
</BrowserRouter>,
document.getElementById('root')
);
App.jsx
import React, { Component } from 'react'
import About from './components/About';
import Home from './components/Home';
import { Link,Route} from "react-router-dom";
export default class App extends Component {
render() {
return (
<div>
<div>
{/* 标题 */}
<div className="row">
<div className="col-xs-offset-2 col-xs-8">
<div className="page-header"><h2>React Router Demo</h2></div>
</div>
</div>
{/* 导航栏 */}
<div className="row">
<div className="col-xs-2 col-xs-offset-2">
<div className="list-group">
{/*编写路由链接*/}
<Link className="list-group-item" to="/about">About</Link>
<Link className="list-group-item" to="/Home">Home</Link>
</div>
</div>
{/* 内容区 */}
<div className="col-xs-6">
<div className="panel">
<div className="panel-body">
{/*注册路由*/}
<Route path='/home' component={Home}/>
<Route path='/about' component={About}/>
</div>
</div>
</div>
</div>
</div>
</div>
)
}
}
<App>
外侧需要包裹一个<BrowserRouter>
或者<HashRouter>
标签history:
go: ƒ go(n)
goBack: ƒ goBack()
goForward: ƒ goForward()
push: ƒ push(path, state)
replace: ƒ replace(path, state)
location:
pathname: "/home/message/detail/2/hello"
search: ""
state: undefined
match:
params: {}
path: "/home/message/detail/:id/:title"
url: "/home/message/detail/2/hello"
NavLink是Link的迭代,NavLink 可以通过 activeClassName 属性指定点击之后追加的样式名,默认追加类名 active ,
<NavLink activeClassName="demo" to="/about">About</NavLink>
<NavLink activeClassName="demo" to="/home">Home</NavLink>
封装NavLink: MyNavLink.jsx
/*
针对 NavLink 的二次封装
*/
import React, { Component } from 'react'
import { NavLink } from "react-router-dom";
export default class MyNavLink extends Component {
render() {
let {} = this.props
return (
// <NavLink className="list-group-item" {...this.props}>{this.props.children}</NavLink>
<NavLink className="list-group-item" {...this.props} />
)
}
}
调用方式
<MyNavLink to="/11">MyNavLink</MyNavLink>
import { Route,NavLink,Switch } from "react-router-dom";
{/* ... */}
{/* 编写路由链接 */}
<NavLink to="/about">About</NavLink>
<NavLink to="/home">Home</NavLink>
{/* 注册路由 包裹 Switch 标签 */}
<Switch>
<Route path='/home' component={Home}/>
<Route path='/about' component={About}/>
</Switch>
当路由路径中出现多级路由时,如<Route path='/home/index' component={Home} />
时,页面加载后可能会出现样式文件丢失
三种解决方案:
<link rel="stylesheet" href="/bootstrap.css">
<link rel="stylesheet" href="%PUBLIC_URL%/bootstrap.css">
import { HashRouter } from "react-router-dom";
ReactDOM.render(
<HashRouter>
<App/>
</HashRouter>,
document.getElementById('root')
);
模糊匹配(左侧匹配)示例
<NavLink to="/home/a">Home</NavLink>
<Switch>
{/* 可以匹配 */}
<Route path='/home' component={Home}/>
</Switch>
开启严格匹配示例
<NavLink to="/about">Home</NavLink>
<Switch>
{/* 可以匹配 */}
<Route exact path="/about" component={About}/>
{/* 不可以匹配 */}
<Route exact path="/about/a" component={About}/>
</Switch>
exact={true}
(语法糖:可以省略属性值) 开启严格匹配:<Route exact path="/about" component={About}/>
一般写在所有路由注册的最下方,当所有路由都无法匹配时,跳转到 Redirect 指定的路由
import { Route,Switch,Redirect } from "react-router-dom";
<Switch>
<Route path="/about" component="{About}" />
<Route path="/home" component="{Home}" />
<Redirect to="/about" />
</Switch>
注册子路由需写上父路由的 path 路由的匹配是按照注册路由的顺序进行的 home.jsx | 不要开启严格匹配
<!-- 父组件 -->
<MyNavLink to="/about">About</MyNavLink>
<MyNavLink to="/home">Home</MyNavLink>
<Switch>
<Route path="/about" component="{About}" />
<Route path="/home" component="{Home}" />
<Redirect to="/about" />
</Switch>
/pages/home/news.jsx & ../message.jsx
<!-- 子组件 -->
<ul className="nav nav-tabs">
<li>
<MyNavLink to="/home/news">News</MyNavLink>
</li>
<li>
<MyNavLink to="/home/message">Message</MyNavLink>
</li>
</ul>
<Switch>
<Route path="/home/news" component="{News}" />
<Route path="/home/message" component="{Message}" />
<Redirect to="/home/news" />
</Switch>
三种方式:params, search, state 参数 state 方式当前页面刷新可保留参数,但在新页面打开不能保留。 params 和 search 参数都会变成字符串,两种方式由于参数保存在 URL 地址上,因此都能保留参数。
{/* 向路由组件传递 params 参数 */}
<Link to={`/home/message/detail/${msgObj.id}/${msgObj.title}`}>{msgObj.title}</Link>
{/* 声明接收 params 参数 */}
<Route path='/home/message/detail/:id/:title' component={Detail}/>
// 接收 params 参数
const {id,title} = this.props.match.params
{/* 向路由组件传递 search 参数 */}
<Link to={`/home/message/detail/?id:${msgObj.id}/${msgObj.title}`}>{msgObj.title}</Link>
{/* search 参数无需声明接收 */}
<Route path='/home/message/detail' component={Detail}/>
// 接收 params 参数(需要自己处理 字符串截取转对象)
const {search } = this.props.location //search:"?id=01&title=消息1"
// 处理 search 参数
let searchObj = {}
var splitArr = search.slice(1,search.length).split("&") //['id=03','title=消息3']
splitArr.map((item) => {
let Objkey,Objvalue = ''
let Obj = {}
item.split("=").map((value,index) => {
index==0 ? Objkey = value : Objvalue = value
})
Obj[Objkey] = Objvalue
searchObj = {...searchObj,...Obj}
})
{/* 向路由组件传递 state 参数 */}
<Link to={{pathname:'/home/message/detail',state:{ id:msgObj.id,title:msgObj.title }}}>{msgObj.title}</Link>
{/* state 参数也无需声明接收 */}
<Route path='/home/message/detail' component={Detail}/>
// 接收 state 参数
const { id,title } = this.props.location.state || {}
补充:开启replace模式 ,rreact-router-dom默认启用push模式 push与replace的区别 push 压栈操作,会产生历史记录 replace 替换操作,不会产生历史记录,无法返回
<Link replace={true} to='/home/message/detail'>{msgObj.title}</Link>
// 简写( == 严格匹配模式)
<Link replace to='/home/message/detail'>{msgObj.title}</Link>
通过 history 对象提供的api实现路由跳转 history: props.history.go: ƒ go(n) 参数传递整数(正数前进 负数后退) props.history.goBack: ƒ goBack() 前进 props.history.goForward: ƒ goForward() 后退 props.history.push: ƒ push(path, state) 添加历史记录 props.history.replace: ƒ replace(path, state) 替换历史记录
// 三种编程式导航传参的方式
this.props.history.push(`/home/message/detail/${id}/${title}`)
this.props.history.push(`/home/message/detail?id=${id}&title=${title}`)
this.props.history.push(`/home/message/detail`, { id: id, title: title })
withRouter 的作用:加工一般组件,让其拥有路由组件的 API ,如 this.props.history.push 等。
import React, { Component } from 'react'
import { withRouter } from 'react-router-dom'
class Header extends Component {
render() {
{/*...*/}
}
}
export default withRouter(Header)
npm install antd --save
import React, { Component } from 'react'
// 引入antd
import { Button } from 'antd';
import { PoweroffOutlined }from '@ant-design/icons';
import "antd/dist/antd.css";
export default class App extends Component {
render() {
return (
<div>
<Button type="primary" icon={<PoweroffOutlined />}>
Click me!
</Button>
</div>
)
}
}
npm install react-app-rewired customize-cra babel-plugin-import
/* package.json */
"scripts": {
- "start": "react-scripts start",
+ "start": "react-app-rewired start",
- "build": "react-scripts build",
+ "build": "react-app-rewired build",
- "test": "react-scripts test",
+ "test": "react-app-rewired test",
}
//配置具体的修改规则
const { override, fixBabelImports, addLessLoader } = require('customize-cra')
module.exports = override(
fixBabelImports('import', {
libraryName: 'antd',
libraryDirectory: 'es',
style: 'css',
})
)
何时用 Redux ?
store.js
// 引入 createStore 创建 store 对象
import { createStore } from "redux";
// 引入为 Count 服务的 countReducer
import countReducer from "./reduce";
// 暴露 store
export default createStore(countReducer)
reduce.js
/*
为Count组件创建的服务reducer
作用:
初始化store
接收两个参数:之前的状态(preState),动作对象(action)
*/
const initState = 0
export default function countReducer(preState = initState,action) {//默认参数
const { type,data } = action
switch (type) {
case 'increment':
return data + preState
default:
return preState//无参数,返回默认值 0
}
}
index.jsx
import React, { Component } from 'react'
import store from "../../redux/store";
export default class Count extends Component {
componentDidMount(){
// 组件挂载完毕,监测状态更新,重新渲染
store.subscribe(()=>{
this.setState({})
})
}
// 加
increment = ()=>{
// 获取用户输入数据
const userInput = (this.selectNumber.value)*1
// 通知redux 更新状态
store.dispatch({type:'increment',data:userInput})
}
render() {
return (
<div className='Count'>
<p>
当前求和结果为:Redux:{store.getState()}
</p>
<br/>
<select ref={c => this.selectNumber = c}>
<option>1</option>
<option>2</option>
</select>
<button onClick = {this.increment}>+</button>
</div>
)
}
}
补充:监测redux中数据改变重新绘排,可以在index.js入口文件中监测整个App
// ...
store.subscribe(()=>{
ReactDom.render(<App/>,document.querySelector('#root'))
})
// redux/count_action.js
import { INCREMENT, DECREMENT } from './constant'
export const createIncrementAction = (data) => ({ type: INCREMENT, data })
export const createDecrementAction = (data) => ({ type: DECREMENT, data })
// redux/constant.js
// 保存常量值
export const INCREMENT = 'increment'
export const DECREMENT = 'decrement'
要点:
npm install redux-thunk -S
// count_action.js
import { INCREMENT, DECREMENT } from './constant.js'
// 同步 action 返回一个对象
export const createIncrementAction = (data) => ({ type: INCREMENT, data })
export const createDecrementAction = (data) => ({ type: DECREMENT, data })
// 异步 action 返回一个函数
export const createIncrementAsyncAction = (data, time) => {
return (dispatch) => {
setTimeout(() => {
dispatch(createIncrementAction(data))
}, time)
}
}
// store.js
// 引入 applyMiddleware 异步支持函数
import { createStore, applyMiddleware } from 'redux'
import countReducer from './count_reducer'
import thunk from 'redux-thunk'
export default createStore(countReducer, applyMiddleware(thunk))
// Count.jsx
incrementAsync = () => {
const { value } = this.selectNumber
store.dispatch(createIncrementAsyncAction(value * 1))
}
<button onClick = {this.incrementAsync}>异步加</button>
总结: store 在分发 action 时,发现返回一个函数(为异步 action)。因此 store 执行这个函数,同时给这个函数默认传递 dispatch 参数,等待异步任务完成取到数据后,直接调用 dispatch 方法分发同步 action 。
Redux 官方提供的 React 绑定库。 具有高效且灵活的特性。 react-redux 中文文档
安装: npm install react-redux
store.js
// 引入 createStore 创建 store 对象
import { createStore } from "redux";
// 引入为 Count 服务的 countReducer
import countReducer from "./count_reduce";
// 暴露 store
export default createStore(countReducer)
count_action.js
import { INCREMENT,DECREMENT } from "./constant";
export const createIncrementAction = data => ( {type:INCREMENT,data:data} )
export const createDecrementAction = data => ( {type:DECREMENT,data:data} )
count_reducer.js
/*
为Count组件创建的服务reducer
作用:
初始化store
接收两个参数:之前的状态(preState),动作对象(action)
*/
import { INCREMENT,DECREMENT } from "./constant";
const initState = 0 //初始化默认参数
export default function countReducer(preState = initState,action) {//默认参数
const { type,data } = action
switch (type) {
case INCREMENT:
return data + preState
case DECREMENT:
return preState - data
default:
return preState//无参数,返回默认值 0
}
}
App.jsx
/*
为容器组件传入store 并渲染
*/
import React, { Component } from 'react'
import store from "./redux/store";
import Count from "./containers/Count";
export default class App extends Component {
render() {
return (
<div>
<h3>求和案例-react实现</h3>
<hr/>
<Count store={store}/>
</div>
)
}
}
Count.jsx
/*
CountUI容器组件
引入connet 链接ui组件与redux
映射状态和状态方法
*/
import CountUI from "../../components/Count";
import { connect } from "react-redux";
import { createIncrementAction,createDecrementAction,createIncrementActionAsync } from "../../redux/count_action";
// 映射状态
function mapStateToProps(state) {
return {count:state}
}
// 映射操作状态的方法
function mapDispathToProps(dispath) {
return {
increment: inputNumber => {dispath(createIncrementAction(inputNumber))},
decrement: inputNumber => {dispath(createDecrementAction(inputNumber))},
incrementAsync: (inputNumber,time) => {dispath(createIncrementActionAsync(inputNumber,time))},
}
}
// 映射状态和方法 并暴露容器组件
export default connect(
mapStateToProps,
mapDispathToProps
)(CountUI)
CountUI.jsx
import React, { Component } from 'react'
import './Count.css'
export default class Count extends Component {
// 加
increment = ()=>{
const userInput = (this.selectNumber.value)*1
this.props.increment(userInput)
}
// 减
decrement = ()=>{
const userInput = (this.selectNumber.value)*1
this.props.decrement(userInput)
}
// 奇数加
incrementIfOdd = ()=>{
const userInput = (this.selectNumber.value)*1
if (this.props.count % 2 !== 0) {
this.props.increment(userInput)
}
}
// 异步加
incrementAsync = ()=>{
const userInput = (this.selectNumber.value)*1
this.props.incrementAsync(userInput,500)
}
render() {
// const userInput = (this.selectNumber.value)*1
const {count} = this.props
return (
<div className='Count'>
<p>
当前求和结果为:{count}
</p>
<br/>
<select ref={c => this.selectNumber = c}>
<option>1</option>
<option>2</option>
<option>3</option>
</select>
<button onClick = {this.increment}>+</button>
<button onClick = {this.decrement}>-</button>
<button onClick = {this.incrementIfOdd}>求和结果为奇数加</button>
<button onClick = {this.incrementAsync}>异步加</button>
</div>
)
}
}
...
export default connect(
state => ({count:state}),// 映射状态
{
increment:createIncrementAction,
decrement:createDecrementAction,
incrementAsync:createIncrementActionAsync
} //映射状态的简写方式(对象 react-redux 会自动 分发dispath)
)(Count)
import React, { Component } from 'react'
import {
createIncrementAction,
createDecrementAction,
} from '../../redux/count_action'
import {connect} from 'react-redux'
// 定义 UI 组件
class Count extends Component {
...
}
// 创建容器组件
export default connect(
state => ({count: state}),
{
add: createIncrementAction,
sub: createDecrementAction
}
)(Count)
文件结构, 容器组件和 UI 组件合为一体后放在 containers 文件夹(存放react-redux管理的需要管理状态的组件)。 redux 文件夹新建 actions 和 reducers 文件夹分别用于存放每个容器组件需要的 action 和 reducer 。 重点:在 store.js 中引入 combineReducers() 整合多个 reducer 来合并总状态对象,组件中通过对象的形式访问 redux/store.js | redux中管理状态的store模块
// 引入 createStore 创建 store 对象
import { createStore,applyMiddleware,combineReducers } from "redux";
// 引入 redux-thunk , 用于支持异步action
import thunk from 'redux-thunk'
// 引入为 Count 服务的 countReducer
import countReducer from "./reducers/count"
// 引入为 Persion 服务的 countReducer
import persionReducer from "./reducers/persion"
// 汇总 reducers
const allReducers = combineReducers({
sum: countReducer,//合并后的key 会作为总状态对象中的key,value为状态
persions: persionReducer
})
// 暴露 store
export default createStore(allReducers,applyMiddleware(thunk))
redux/actions/persion.js | 创建action对象模块
import { ADDPERSION } from "../constant";
export const createAddPersionAction = data => ({ type:ADDPERSION,data })
redux/reducers/persion.js | 初始化状态和处理状态的reducer模块
import { ADDPERSION } from "../constant";
const initState = [{id:136780,name:'root',age:'0'}]
export default function persionReducer(preState=initState,action) {
const { type,data } = action
switch (type) {
case ADDPERSION:
return [data,...preState]
default:
return preState
}
}
redux/constant.js | redux中的常量模块
export const INCREMENT = 'increment'
export const DECREMENT = 'decrement'
export const ADDPERSION = 'addPersion'
containers/Persion/index.jsx | UI组件和容器组件
import React, { Component } from 'react'
import { connect } from "react-redux";
import { nanoid } from "nanoid";
import { createAddPersionAction } from "../../redux/actions/persion";
import './Persion.css'
class demoUI extends Component {
addPersion = ()=>{
let name = this.nameNode.value
let age = this.ageNode.value
const persionObj = {id:nanoid(),name,age}
this.props.addPersionObj(persionObj)
}
render() {
return (
<div>
<h2>Perssion组件,上方组件总和为{this.props.sum}</h2>
<input ref = {c => this.nameNode = c} type="text" id="name" placeholder='请输入你的名字' />
<input ref = {c => this.ageNode = c} type="text" id="age" placeholder='请输入你的年龄'/>
<button onClick={this.addPersion}>添加</button>
<ul>
{
this.props.persionObjArr.map((persionObj)=>{
return <li key={persionObj.id}> 姓名:{persionObj.name} ,年龄:{persionObj.age} </li>
})
}
</ul>
</div>
)
}
}
export default connect(
state => ({
persionObjArr:state.persions ,
sum:state.sum ,
}),
{ addPersionObj: createAddPersionAction }
)(demoUI)
let arr = [1,2]
// 给arr数组追加并return,下面两种方式的区别
return ['a','b',...arr]
arr.unshift('a','b')
return arr
/*
返回的内存地址不同
*/
npm i redux-devtools-extension --save-dev
,redux/store.js
import { composeWithDevTools } from 'redux-devtools-extension'
...
//需要异步中间件
export default createStore(Reducers, composeWithDevTools(applyMiddleware(thunk)))
// 不需要异步中间件
export default createStore(Reducers, composeWithDevTools())
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。