首先,安装一个轻量级框架axios
yarn add axios
在App组件中定义两个简单的按钮分别用来获取学生数据和汽车数据。在测试时,需要打开测试服务器进行监听。
import React, { Component } from 'react'
import axios from 'axios'
export default class App extends Component {
getStudentData = () => {
axios.get('http://localhost:3000/api1/students').then(
response => {console.log('成功了', response.data);},
error => {console.log('失败了', error);}
)
}
getCarData = () => {
axios.get('http://localhost:3000/api2/cars').then(
response => {console.log('成功了', response.data)},
error => {console.log('失败了', error);}
)
}
render() {
return (
)
}
}
进行脚手架的配置,在src文件夹下创建setupProxy.js文件。因为配置文件一个项目只需要写一次,所以每次只需要把该文件进行复制粘贴即可。配置文件如下:(该项目因为向两个不同的服务器发送了请求,所以设置了两个代理)
const proxy = require('http-proxy-middleware')
module.exports = function(app) {
app.use(
proxy('/api1', { //遇见'/api1'前缀的请求,就会触发该代理配置
target: 'http://localhost:5000', //请求转发给谁
changeOrigin: true, //控制服务器收到的响应头中Host字段的值(这条写不写影响不大)
pathRewrite: {'^/api1':''} //重写请求路径(必须的,用于走代理时将'/api1'换成空字符串)
}),
proxy('/api2', {
target: 'http://localhost:5001',
changeOrigin: true,
pathRewrite: {'^/api2':''}
})
)
}
代理配置总结
方法1:
在package.json中追加如下配置
"proxy":"http://localhost:5000"
说明:
方法2:
第一步:创建代理配置文件
在src下创建配置文件:src/setupProxy.js
编写setupProxy.js配置具体代理规则:
const proxy = require('http-proxy-middleware')
module.exports = function(app) {
app.use(
proxy('/api1', { //api1是需要转发的请求(所有带有/api1前缀的请求都会转发给5000)
target: 'http://localhost:5000', //配置转发目标地址(能返回数据的服务器地址)
changeOrigin: true, //控制服务器接收到的请求头中host字段的值
/*
changeOrigin设置为true时,服务器收到的请求头中的host为:localhost:5000
changeOrigin设置为false时,服务器收到的请求头中的host为:localhost:3000
changeOrigin默认值为false,但我们一般将changeOrigin值设为true
*/
pathRewrite: {'^/api1': ''} //去除请求前缀,保证交给后台服务器的是正常请求地址(必须配置)
}),
proxy('/api2', {
target: 'http://localhost:5001',
changeOrigin: true,
pathRewrite: {'^/api2': ''}
})
)
}
说明:
将静态页面拆成React的Search、Item、List组件
将公共的样式放在App.css中,单独的样式也拆到各自的index.css里(该案例样式较为简单,所以只有List组件中有样式)
state都定义在App组件中方便进行修改;App组件中定义了一个修改state的方法updateAppState并将其传给了Search组件
import React, { Component } from 'react'
import Search from './components/Search'
import List from './components/List'
export default class App extends Component {
state = { //初始化状态
users: [], //users初始值为数组
isFirst: true, //是否为第一次打开页面
isLoading: false, //标识是否处于加载中
err: '', //存储请求相关的错误信息
}
updateAppState = (stateObj) => {
this.setState(stateObj)
}
render() {
return (
)
}
}
Search组件
导入axios框架用于向github接口发送get请求;
利用ref核心属性给Search实例对象添加keyWordElement属性,该属性对应着input标签。同时keyWordElement是一个对象,它有一个value属性,该属性对应着input标签中输入的文字。
我们将该value属性重新赋值为keyword,并通过axios将关键字作为参数发送出去
import React, { Component } from 'react'
import axios from 'axios'
export default class Search extends Component {
search = () => {
// 获取用户的输入(连续解构赋值+重命名)
const {keyWordElement: {value:keyword}} = this
// 发送请求前通知App更新状态
this.props.updateAppState({isFirst:false, isLoading:true})
// 发送网络请求
// 因为我们端口是3001,也从该端口发送请求,所以可以省略http://localhost:3001
axios.get(`/api1/search/users?q=${keyword}`).then(
response => {
// 请求成功后通知APP更新状态
this.props.updateAppState({isLoading:false, users:response.data.items})
// console.log(response);
},
error => {
// 请求失败后通知APP更新状态
this.props.updateAppState({isLoading: false, err: error.message, users: []})
// console.log('失败了', error);
}
)
}
render() {
return (
搜索Github用户
this.keyWordElement = c} type="text" placeholder="输入关键字点击搜索"/>
)
}
}
List组件
App组件通过展开运算符把state所有属性通过props传递给List组件
List通过解构赋值拿到必要的属性
jsx只能写js表达式,而不能写for/if/switch这类js语句。故条件判断这里使用三元运算符。重点:三元表达式可以进行连续判断,效果相当于if语句
利用map对users数组进行处理,每个元素单独返回,并通过props将所有user属性传给Item组件
import React, { Component } from 'react'
import './index.css'
import Item from '../Item'
export default class List extends Component {
render() {
const {isFirst, users, isLoading, err} = this.props
return (
{
isFirst ? 欢迎使用,输入关键字,随后点击搜索
:
isLoading ? 加载中...
:
err ? {err}
:
users.map((user) => {
return
})
}
)
}
}
Item组件
对我们需要的user属性进行解构赋值,包括头像链接、用户仓库链接和用户名
将属性通过{}放在需要显示数据的位置
import React, { Component } from 'react'
export default class Item extends Component {
render() {
const {avatar_url, html_url, login} = this.props
return (
)
}
}
代理文件
const proxy = require('http-proxy-middleware')
module.exports = function(app) {
app.use(
proxy('/api1', { //遇见'/api1'前缀的请求,就会触发该代理配置
target: 'http://localhost:5000', //请求转发给谁
changeOrigin: true, //控制服务器收到的响应头中Host字段的值(这条写不写影响不大)
pathRewrite: {'^/api1':''} //重写请求路径(必须的,用于走代理时将'/api1'换成空字符串)
})
)
}
总结
使用PubSub
安装pubsub-js
yarn install pubsub-js
使用方法(https://github.com/mroderick/PubSubJS)
PubSub简化了App组件,因为该方法可以实现兄弟组件间的通信
简化的App组件
import React, { Component } from 'react'
import Search from './components/Search'
import List from './components/List'
export default class App extends Component {
render() {
return (
)
}
}
Search组件
导入PubSub工具库,导入axios框架
使用PubSub.publish()方法发布消息,更新兄弟组件List的state
import React, { Component } from 'react'
import PubSub from 'pubsub-js'
import axios from 'axios'
export default class Search extends Component {
search = () => {
// 获取用户的输入(连续解构赋值+重命名)
const {keyWordElement: {value:keyword}} = this
// 发送请求前通知List更新状态
this.props.updateAppState({isFirst:false, isLoading:true})
PubSub.publish('atguigu', {isFirst:false, isLoading:true})
// 发送网络请求
// 因为我们端口是3000,也从该端口发送请求,所以可以省略http://localhost:3000
axios.get(`/api1/search/users?q=${keyword}`).then(
response => {
// 请求成功后通知List更新状态
PubSub.publish('atguigu', {isLoading:false, users:response.data.items})
},
error => {
// 请求失败后通知List更新状态
PubSub.publish('atguigu', {isLoading: false, err: error.message, users: []})
}
)
}
render() {
return (
搜索Github用户
this.keyWordElement = c} type="text" placeholder="输入关键字点击搜索"/>
)
}
}
List组件
state状态放在List组件中
在componentDidMount钩子函数中使用PubSub.subscribe()方法接收消息
在componentWillUnmount钩子函数中取消订阅
import React, { Component } from 'react'
import PubSub from 'pubsub-js'
import './index.css'
import Item from '../Item'
export default class List extends Component {
state = { //初始化状态
users: [], //users初始值为数组
isFirst: true, //是否为第一次打开页面
isLoading: false, //标识是否处于加载中
err: '', //存储请求相关的错误信息
}
componentDidMount() {
this.token = PubSub.subscribe('atguigu', (_, stateObj) => {
// console.log('List组件收到数据了', data);
this.setState(stateObj)
})
}
componentWillUnmount() {
PubSub.unsubscribe(this.token)
}
render() {
const {isFirst, users, isLoading, err} = this.state
return (
{/* jsx只能写js表达式,而不能写for/if/switch这类js语句。故条件判断这里使用三元运算符。三元表达式可以进行连续判断 */}
{
isFirst ? 欢迎使用,输入关键字,随后点击搜索
:
isLoading ? 加载中...
:
err ? {err}
:
users.map((user) => {
return
})
}
)
}
}
Item组件
Item组件还保持跟之前一样。
react的一个插件库。
专门用来实现一个SPA应用。
基于react的项目基本都会用到此库。
安装react-router-dom
yarn add react-router-dom
明确好界面中的导航区、展示区
导航区的a标签改为Link标签
Demo
展示区写Route标签进行路径的匹配
的最外侧包裹了一个或
App组件,导入Link和Route
import React, { Component } from 'react'
import {Link, Route} from 'react-router-dom'
import Home from './components/Home'
import About from './components/About'
export default class App extends Component {
render() {
return (
React Router Demo
{/* 注册路由 */}
)
}
}
components中的Home和About组件
Home组件
import React, { Component } from 'react'
export default class Home extends Component {
render() {
return (
我是Home的内容
)
}
}
About组件
import React, { Component } from 'react'
export default class About extends Component {
render() {
return (
我是About的内容
)
}
}
1.写法不同:
一般组件:
路由组件:
2.存放位置不同:
一般组件:components
路由组件:pages
3.接收到的props不同:
一般组件:写组件标签时传递了什么,就能收到什么
路由组件:接收到三个固定的属性
history:
go: ƒ go(n)
goBack: ƒ goBack()
goForward: ƒ goForward()
push: ƒ push(path, state)
replace: ƒ replace(path, state)
location:
pathname: "/about"
search: ""
state: undefined
match:
params: {}
path: "/about"
url: "/about"
NavLink可以实现路由链接的高亮,通过activeClassName指定样式名。
将导入的Link改为NavLink
若不写activeClassName,则默认对点击的NavLink添加一个active的类。因为这里导入了bootstrap.css样式,里面本身自带有active的样式。
若要自定义样式,可在index.html文件中通过style定义样式
react脚手架
App.jsx组件中的NavLink
Home
About
自定义MyNavLink标签
在components创建MyNavLink文件,在这里对NavLink进行一层封装
App组件的属性皆可以通过props传送过来
{/* 在React中靠路由链接实现切换组件 */}
Home
About
MyNavLink标签中的Home、About会作为children属性传送过来,故利用展开运算符,可以将所有属性以简便的方式赋值给NavLink中的属性,如下是MyNavLink组件
import React, { Component } from 'react'
import {NavLink} from 'react-router-dom'
export default class MyNavLink extends Component {
render() {
// console.log(this.props);
return (
)
}
}
switch的使用
举例说明
如上三个路由显示节点,但有两个路径相同的Route。此时若点击MyNavLink所对应的按钮则会将路径相同的两个Route均render到页面上。
此时可以import {Route, Switch} from ‘react-router-dom’,使用Switch标签将Route标签进行包裹,则在匹配到第一个路径时便会停止进行搜索路径了。
使用Switch的优点
出现问题的原因
想要实现在所有路径前加一个固定的前缀,如公司名atguigu。则会出现css样式丢失的问题。
Home
About
.....其他代码......
解决方案
打开public文件夹下的index.html
默认的bootstap.css样式导入的方法为,该方法的问题是在进行路由跳转时会在路径中加上/atguigu,则会导致css文件找不到
解决方案为:修改默认路径
1.public/index.html 中 引入样式时不写 ./ 写 / (常用)
2.public/index.html 中 引入样式时不写 ./ 写 %PUBLIC_URL% (常用)
3.使用HashRouter
默认使用的是模糊匹配(简单记:【输入的路径】必须包含要【匹配的路径】,且顺序要一致)
开启严格匹配:在路由中加上exact={true}。也可以就简便写成exact。
//常规写法
//简便写法
严格匹配不要随便开启,需要再开**,有些时候开启会导致无法继续匹配二级路由**。
一般写在所有路由注册的最下方,当所有路由都无法匹配时,跳转到Redirect指定的路由.
使用方法:
import {Route, Switch, Redirect} from 'react-router-dom'
注册子路由时要写上父路由的path值。
路由的匹配是按照注册路由的顺序进行的。
使用案例:在路由组件中再导入新的路由组件,这样的话Route的路径便要填写二级路径了。在home路由组件中编写了News和Message两个新路由组件:
import React, { Component } from 'react'
import {Route, Switch, Redirect} from 'react-router-dom'
import MyNavLink from '../../components/MyNavLink'
import Message from './Message'
import News from './News'
export default class Home extends Component {
render() {
return (
Home组件内容
-
News
-
Message
)
}
}
传递params参数:
基本使用:
路由链接(携带参数):详情
注册路由(声明接收):
接收参数:this.props.match.params
使用案例:
1.在Home组件中定义Message和News组件
下面是News组件:
import React, { Component } from 'react'
export default class News extends Component {
render() {
return (
- news001
- news002
- news003
)
}
}
下面是Message组件:定义了state,state中的数据是用来传递给子组件Detail的;利用map方法对数组中的每一个元素单独返回,并定义id属性和to对应的路径;同时将参数传递给子组件;因为要传递的参数是变量,所以使用``而不是’’。
import React, { Component } from 'react'
import {Link,Route} from 'react-router-dom'
import Detail from './Detail'
export default class Message extends Component {
state = {
messageArr: [
{id: '01', title: '消息1'},
{id: '02', title: '消息2'},
{id: '03', title: '消息3'},
]
}
render() {
const {messageArr} = this.state
return (
{
messageArr.map((msgObj) => {
return (
-
{/* 向路由组件传递params参数 */}
{msgObj.title}
)
})
}
{/* 声明接收params参数 */}
)
}
}
下面是Detail组件:定义了一个数组变量,用来作为显示信息;通过路径传递过来的参数信息可以通过this.props.match.params获取;使用find方法找出id相对应的数组元素;将找出的结果信息进行render,显示到页面上。
import React, { Component } from 'react'
const DetailData = [
{id: '01', content: '你好,中国'},
{id: '02', content: '你好,尚硅谷'},
{id: '03', content: '你好,未来的自己'}
]
export default class Detail extends Component {
render() {
const {id, title} = this.props.match.params
const findResult = DetailData.find((detailObj) => {
return detailObj.id === id
})
// console.log(this.props);
return (
- ID:{id}
- TITLE:{title}
- CONTENT:{findResult.content}
)
}
}
传递search参数
基本使用:
路由链接(携带参数):详情
注册路由(无需声明,正常注册即可):
接收参数:this.props.location.search
备注:获取到的search是urlencoded编码字符串,需要借助querystring解析
使用案例:
案例还是如上传递params参数的案例。区别点在于:
1.携带参数和注册路由方式不同。
2.传递到Detail组件的参数的获取位置不同。
3.参数在this.props.location中并且是以字符串的形式存在的,如search: “?id=01&title=消息1”
4.所以需要对字符串进行剪切,并借助querystring解析将其转换成对象
携带参数:
{/* 向路由组件传递params参数 */}
{/* {msgObj.title} */}
{/* 向路由组件传递search参数 */}
{msgObj.title}
注册路由:
{/* 声明接收params参数 */}
{/* */}
{/* search参数无需声明接收,正常注册路由即可 */}
Detail组件:
import React, { Component } from 'react'
import qs from 'querystring'
const DetailData = [
{id: '01', content: '你好,中国'},
{id: '02', content: '你好,尚硅谷'},
{id: '03', content: '你好,未来的自己'}
]
export default class Detail extends Component {
render() {
// const {id, title} = this.props.match.params
// console.log(this.props);
// 接收params参数
// const {id, title} = this.props.match.params
// 接收search参数
const {search} = this.props.location
const {id, title} = qs.parse(search.slice(1))
const findResult = DetailData.find((detailObj) => {
return detailObj.id === id
})
// console.log(this.props);
return (
- ID:{id}
- TITLE:{title}
- CONTENT:{findResult.content}
)
}
}
传递state参数
基本使用:
路由链接(携带参数):详情
注册路由(无需声明,正常注册即可):
接收参数:this.props.location.state
备注:刷新也可以保留住参数
使用案例:
案例还是如上传递params参数的案例。区别点在于:
1.携带参数和注册路由方式不同。
2.传递到Detail组件的参数的获取位置不同。
3.参数存在this.props.location.state中
携带参数:
{msgObj.title}
注册路由:
{/* state参数无需声明接收,正常注册路由即可 */}
Detail组件:
import React, { Component } from 'react'
// import qs from 'querystring'
const DetailData = [
{id: '01', content: '你好,中国'},
{id: '02', content: '你好,尚硅谷'},
{id: '03', content: '你好,未来的自己'}
]
export default class Detail extends Component {
render() {
// const {id, title} = this.props.match.params
// console.log(this.props);
// 接收params参数
// const {id, title} = this.props.match.params
// 接收search参数
// const {search} = this.props.location
// const {id, title} = qs.parse(search.slice(1))
// 接收state参数
const {id, title} = this.props.location.state || {} //若state为空,则传递一个空属性。避免报错
const findResult = DetailData.find((detailObj) => {
return detailObj.id === id
}) || {}
// console.log(this.props);
return (
- ID:{id}
- TITLE:{title}
- CONTENT:{findResult.content}
)
}
}
push模式
push模式也就是一般的使用情况,程序的几次路由跳转会被压进栈中。所以点击后退时会依次返回之前的路由。
replace模式
在Link标签中加入replace={true},该路由在点击后则不会留下痕迹。点击回退也不会回到该路由所处位置。(简便模式,直接写一个replace在标签中也行)
{msgObj.title}
我们可以通过使用this.props.history对象上的API对操作路由进行跳转、前进和后退操作。
实例操作:
在Message组件的消息后面添加两个button按钮;
按钮绑定点击事件,点击事件通过绑定箭头函数传入两个参数;
分别定义使用push和replace两种方式进行跳转的方法;
在Route中定义前进、后退和go方法的按钮;
import React, { Component } from 'react'
import {Link,Route} from 'react-router-dom'
import Detail from './Detail'
export default class Message extends Component {
state = {
messageArr: [
{id: '01', title: '消息1'},
{id: '02', title: '消息2'},
{id: '03', title: '消息3'},
]
}
pushShow = (id, title) => {
//push跳转+携带params参数
// this.props.history.push(`/home/message/detail/${id}/${title}`)
// push跳转+携带search参数
// this.props.history.push(`/home/message/detail/?id=${id}&title=${title}`)
// replace跳转+携带state参数
this.props.history.push('/home/message/detail', {id, title})
}
replaceShow = (id, title) => {
//replace跳转+携带params参数
// this.props.history.replace(`/home/message/detail/${id}/${title}`)
//replace跳转+携带search参数
// this.props.history.replace(`/home/message/detail/?id=${id}&title=${title}`)
// replace跳转+携带state参数
this.props.history.replace('/home/message/detail', {id, title})
}
back = () => {
// console.log(this.props.history);
this.props.history.goBack()
}
forward = () => {
this.props.history.goForward()
}
go = () => {
this.props.history.go(2)
}
render() {
const {messageArr} = this.state
return (
{
messageArr.map((msgObj) => {
return (
-
{/* 向路由组件传递params参数 */}
{/* {msgObj.title} */}
{/* 向路由组件传递search参数 */}
{/* {msgObj.title} */}
{/* 向路由组件传递state参数 */}
{msgObj.title}
)
})
}
{/* 声明接收params参数 */}
{/* */}
{/* search参数无需声明接收,正常注册路由即可 */}
{/* */}
{/* state参数无需声明接收,正常注册路由即可 */}
)
}
}
相比与一般组件,路由组件可以接收到不同的props。
当我们想要一般组件也可以拥有和路由组件一样props时(这样便可以调用props中的方法了。)
实例操作:
在Header组件中添加前进、回退两个API按钮
导入withouRouter方法,并将调用withRouter方法后的Header组件导出
import React, { Component } from 'react'
import {withRouter} from 'react-router-dom'
class Header extends Component {
back = () => {
this.props.history.goBack()
}
forward = () => {
this.props.history.goForward()
}
render() {
console.log(this.props);
return (
React Router Demo
)
}
}
export default withRouter(Header)
// withRouter可以加工一般组件,让一般组件具备路由组件所特有的API
// withRouter的返回值是一个新组件
AntDesign官网:https://ant.design/index-cn。可以在官网的“在create-react-app中使用”查看具体的使用步骤。
安装依赖:
yarn add antd
引入样式:
import 'antd/dist/antd.css'
Ant Design样式的使用——按钮
在App组件中添加按钮:
import React, { Component } from 'react'
import { Button} from 'antd';
import 'antd/dist/antd.css'
export default class App extends Component {
render() {
return (
)
}
}
Ant Design样式的使用——图标
在App组件中添加图标:
import React, { Component } from 'react'
import { Button } from 'antd';
import 'antd/dist/antd.css'
import {WechatOutlined, WeiboOutlined, SearchOutlined} from '@ant-design/icons'
export default class App extends Component {
render() {
return (
)
}
}
antd的高级配置:
通过按需引入样式,我们就不需要将样式全部导入了,可以将import 'antd/dist/antd.css’这行代码进行注销。
自定义主题:
按照 配置主题 的要求,自定义主题需要用到类似 less-loader 提供的 less 变量覆盖功能。我们可以引入 craco-less 来帮助加载 less 样式和修改变量。
首先把 src/App.css
文件修改为 src/App.less
,然后修改样式引用为 less 文件。
/* src/App.js */
- import './App.css';
+ import './App.less';
/* src/App.less */
- @import '~antd/dist/antd.css';
+ @import '~antd/dist/antd.less';
然后安装 craco-less
并修改 craco.config.js
文件如下。
$ yarn add craco-less
const CracoLessPlugin = require('craco-less');
module.exports = {
plugins: [
{
plugin: CracoLessPlugin,
options: {
lessLoaderOptions: {
lessOptions: {
modifyVars: { '@primary-color': '#1DA57A' },
javascriptEnabled: true,
},
},
},
},
],
};
这里利用了 less-loader 的 modifyVars
来进行主题配置,变量和其他配置方式可以参考 配置主题 文档。修改后重启 yarn start
,如果看到一个绿色的按钮就说明配置成功了