react脚手架
01-react脚手架
1.xxx脚手架: 用来帮助程序员快速创建一个基于xxx库的模板项目
- 1.包含了所有需要的配置(语法检查、jsx编译、devServer…)
- 2.下载好了所有相关的依赖
- 3.可以直接运行一个简单效果
2.react提供了一个用于创建react项目的脚手架库:
create-react-app
3.项目的整体技术架构为:react + webpack + es6 + eslint
4.使用脚手架开发的项目的特点: 模块化, 组件化, 工程化
02-创建项目并启动
- 第一步,全局安装:
npm i -g create-react-app
- 第二步,切换到想创项目的目录,使用命令:
create-react-app hello-react
- 第三步,进入项目文件夹:
cd hello-react
- 第四步,启动项目:
npm start
03-react脚手架项目结构
04-功能界面的组件化编码流程(通用)
- 拆分组件: 拆分界面,抽取组件
- 实现静态组件: 使用组件实现静态页面效果
- 实现动态组件
- 动态显示初始化数据
- 数据类型
- 数据名称
- 保存在哪个组件?
- 交互(从绑定事件监听开始)
- 动态显示初始化数据
一、todoList案例相关知识点
- 拆分组件、实现静态组件,注意:className、style的写法
- 动态初始化列表,如何确定将数据放在哪个组件的state中?
–某个组件使用:放在其自身的state中
–某些组件使用:放在他们共同的父组件state中(官方称此操作为:状态提升)- 关于父子之间通信:
1.【父组件】给【子组件】传递数据:通过props传递
2.【子组件】给【父组件】传递数据:通过props传递,要求父 提前给 子 传递一个函数- 注意defaultChecked 和 checked的区别,类似的还有:defaultValue 和 value
- 状态在哪里,操作状态的方法就在哪里
配置代理
App.js中代码(App组件)
import React, { Component } from 'react'
import axios from 'axios'
export default class App extends Component {
// 解决跨域的方式1:配置一个中间代理,它和client的端口一样
// 比如 client:3000
// server:5000
// client向server直接请求数据,会产生跨域,然后在package.json中配置一个"proxy": "http://localhost:5000"
// 此时这个中间代理的端口是3000,client先向中间代理(端口3000)请求数据,所以aixos.get('http://localhost:3000/students')
// 因为请求的/student 3000端口本身没有,所以代理会把请求转发给5000端口
// 但此种配置方式只能配置一种请求地址,如果还需要请求5001就不行了
// "proxy": "http://localhost:5000"
getStudentData = ()=>{
//!! http://localhost:3000/api1/students 这种加了/api1的,是采用的编写setupProxy.js配置多个代理的方法
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 (
<div>
<button onClick={this.getStudentData}>点我获取学生数据</button>
<button onClick={this.getCarData}>点我获取汽车数据</button>
</div>
)
}
}
react脚手架配置代理总结
方法一
在package.json中追加如下配置
"proxy":"http://localhost:5000"
说明:
- 优点:配置简单,前端请求资源时可以不加任何前缀。
- 缺点:不能配置多个代理。
- 工作方式:上述方式配置代理,当请求了3000不存在的资源时,那么该请求会转发给5000 (优先匹配前端资源)
方法二
-
第一步:创建代理配置文件
在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': ''} }) ) }
说明:
- 优点:可以配置多个代理,可以灵活的控制请求是否走代理。
- 缺点:配置繁琐,前端请求资源时必须加前缀。
二、github搜索案例相关知识点
- 设计状态时要考虑全面,例如带有网络请求的组件,要考虑请求失败怎么办
- ES6小知识点:解构赋值+重命名
let obj = {a:{b:1}}
const {a} = obj; //传统解构赋值
const {a:{b}} = obj; //连续解构赋值
const {a:{b:value}} = obj; //连续解构赋值+重命名
- 消息订阅与发布机制
1.先订阅,再发布(理解:隔空对话的感觉)
2.适用于任意组件间通信
3.要在组件的componentWillUnmount中取消订阅
对于兄弟组件之间的传值非常方便!!!!!!
需要引入库:import PubSub from 'pubsub-js'
发布消息
// 发送请求前通知List更新状态
// this.props.updateAppState({isFirst: false,isLoading: true}) //这是通过父组件通过props传递一个函数给子组件调用,从而形成的子 给 父,然后父 传给 兄弟组件 来传递参数
PubSub.publish('atguigu',{isFirst: false,isLoading: true}) // 发布消息
订阅消息
PubSub.subscribe('atguigu',(msg,stateObj) => {
this.setState(stateObj)
})
- fetch发送请求(关注分离的设计思想)
try {
const response = await fetch(`http://localhost:3000/api1/search/users2?q=${keyWord}`)
const data = await response.json()
console.log(data)
} catch (error) {
console.log('请求出错',error)
}
- axios发送网络请求
// 2.发送网络请求
axios.get(`http://localhost:3000/api1/search/users?q=${keyWord}`).then(
response =>{
// console.log('成功了',response.data)
// 请求成功后,把数据传给App更新状态
this.props.updateAppState({users: response.data.items, isLoading: false})
},
error => {
// console.log('失败了',error)
// 请求失败后 通知App更新状态
this.props.updateAppState({isLoading: false, err: error})
}
)
三、路由的基本使用
- 明确好界面中的导航区、展示区
- 导航区的a标签改为Link标签
<Link to="/xxx"> Demo </Link>
- 展示区写Route标签进行路径的匹配
<Route path='/xxx' component={Demo} />
- index.js中的< App >的最外侧包裹了一个或< HashRouter >
ReactDOM.render( <BrowserRouter> <App/> </BrowserRouter>, document.getElementById('root')) ```
export default class App extends Component {
render() {
return (
<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">
{/* 原生HTML中 靠<a>跳转到不同的页面 */}
{/* <a className="list-group-item active" href="./about.html">About</a>
<a className="list-group-item" href="./home.html">Home</a> */}
{/*!!!!!!这里是导航区,填写Link标签!!!!!!*/}
{/* 在react中靠路由链接实现切换组件(导航栏)---编写路由链接 */}
<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标签进行路由的匹配!!!!!!*/}
<Route path="/about" component={About}/>
<Route path="/home" component={Home}/>
</div>
</div>
</div>
</div>
</div>
)
}
}
四、路由组件与一般组件
- 写法不同:
一般组件:<Demo/>
路由组件:<Route path="/demo" component={Demo}/>
- 存放位置不同:
一般组件:components
路由组件:pages
- 接收到的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与封装NavLink
- NavLink可以实现路由链接的高亮,通过
activeClassName
指定样式名 - 标签体内容是一个特殊的标签属性
- 通过
this.props.children
可以获取标签体内容
export default class App extends Component {
render() {
return (
<div>
<Header/>
<div className="row">
<div className="col-xs-2 col-xs-offset-2">
<div className="list-group">
{/* 原生HTML中 靠<a>跳转到不同的页面 */}
{/* <a className="list-group-item active" href="./about.html">About</a>
<a className="list-group-item" href="./home.html">Home</a> */}
{/* 在react中靠路由链接实现切换组件(导航栏)---编写路由链接 */}
<NavLink activeClassName="atguigu" className="list-group-item" to="/about">About</NavLink>
<NavLink activeClassName="atguigu" className="list-group-item" to="/home">Home</NavLink>
</div>
</div>
<div className="col-xs-6">
<div className="panel">
<div className="panel-body">
{/* 注册路由 (内容展示区域) */}
<Route path="/about" component={About}/>
<Route path="/home" component={Home}/>
</div>
</div>
</div>
</div>
</div>
)
}
}
六、Switch的使用
- 通常情况下,path和component是一一对应的关系
- Switch可以提高路由匹配效率(单一匹配)
{/* 注册路由 (内容展示区域) */}
<Switch>
<Route path="/about" component={About}/>
<Route path="/home" component={Home}/>
<Route path="/home" component={Test}/>
</Switch>
七、解决多级路径 刷新页面 样式丢失的问题
public/index.html
中 引人样式时不写 ./ 而写/
(常用)public/index.html
中 引入样式时不写 ./ 而写%PUBLIC_URL%
(常用)- 使用
HashRouter
(因为HashRouter中会把路径加上’#’, 会认为’#'后的路径就不是服务器的路径了)
八、路由的严格匹配与模糊匹配
-
默认使用的是模糊匹配(简单记:【输入的路径】必须包含要【匹配的路径】,且顺序要一致)
比如下面这种情况是可以匹配上的:
<MyNavLink to="/home/a/b">Home</MyNavLink>
【输入的路径】
<Route exact path="/home" component={Home}/>
【匹配的路径】 -
开启严格匹配:
-
严格匹配不要随便开启,需要再开,有些时候开启会导致无法继续匹配二级路由
九、Redirect的使用
- 一般写在所有路由注册的最下方,当所有路由都无法匹配时,跳转到Redirect指定的路由
- 具体编码:
<Switch>
<Route path="/about" component={About}/>
<Route path="/home" component={Home}/>
<Redirect to="/about"/>
</Switch>
十、嵌套路由
- 注册子路由时要写上父路由的path值
- 路由的匹配是按照注册路由的顺序进行的
App组件(全局父组件)
export default class App extends Component {
render() {
return (
<div>
<Header/>
<div className="row">
<div className="col-xs-2 col-xs-offset-2">
<div className="list-group">
{/* 原生HTML中 靠<a>跳转到不同的页面 */}
{/* <a className="list-group-item active" href="./about.html">About</a>
<a className="list-group-item" href="./home.html">Home</a> */}
{/* 在react中靠路由链接实现切换组件(导航栏)---编写路由链接 */}
<MyNavLink to="/about">About</MyNavLink>
<MyNavLink to="/home">Home</MyNavLink>
</div>
</div>
<div className="col-xs-6">
<div className="panel">
<div className="panel-body">
{/* 注册路由 (内容展示区域) */}
<Switch>
<Route path="/about" component={About}/>
<Route path="/home" component={Home}/>
<Redirect to="/home"/>
</Switch>
</div>
</div>
</div>
</div>
</div>
)
}
}
Home组件(APP组件的儿子)
export default class Home extends Component {
render() {
return (
<div>
<h3>我是Home的内容</h3>
<div>
<ul className="nav nav-tabs">
{/* 在react中靠路由链接实现切换组件(导航栏)---编写路由链接 */}
<li>
<MyNavLink to="/home/news">News</MyNavLink>
{/* <a className="list-group-item active" href="./home-news.html">News</a> */}
</li>
<li>
<MyNavLink to="/home/message">Message</MyNavLink>
{/* <a className="list-group-item " href="./home-message.html">Message</a> */}
</li>
</ul>
</div>
{/* 注册路由 (内容展示区域) */}
<Switch>
<Route path="/home/news" component={News}/>
<Route path="/home/message" component={Message}/>
<Redirect to="/home/news"/>
</Switch>
</div>
)
}
}
News组件(APP组件的孙子,Home组件的儿子)
export default class News extends Component {
render() {
return (
<ul>
<li>news001</li>
<li>news002</li>
<li>news003</li>
</ul>
)
}
}
十一、向路由组件传递参数
-
params参数
- 路由链接(携带参数):
<Link to="/demo/test/tom/18">详情</Link>
- 注册路由(声明接收):
<Route path="/demo/test/:name/:age" component={Test}/>
- 接收参数:
this.props.match.params
- 路由链接(携带参数):
-
search参数
- 路由链接(携带参数):
<Link to={ /demo/test?name=tom&age=18 }>详情</Link>
- 注册路由(无需声明接收,正常注册路由即可):
<Route path="/demo/test" component={Test}/>
- 接收参数:
this.props.location.search
- 备注:获取到的search是urlencode编码字符串,需要借助querystring解析
- 路由链接(携带参数):
-
state参数
- 路由链接(携带参数):
<Link to={{pathname:'/demo/test',state:{name:'tom',age:18}}}> 详情 </Link>
- 注册路由(无需声明接收,正常注册路由即可):
- 接收参数:
this.props.location.state
- 备注:刷新也可以保留住参数
- 路由链接(携带参数):
十二、编程式路由导航
借助this.props.history
对象上的API操作路由跳转、前进、后退
this.props.history.push()
this.props.history.replace()
this.props.history.goBack()
this.props.history.goForward()
this.props.history.go()
十三、BrowserRouter与HashRouter的区别
- 底层原理不一样:
BrowserRouter
使用的是H5的history API
,不兼容IE9及以下的版本
HashRouter
使用的是URL
的哈希值 - path表现形式不一样
BrowserRouter
的路径中 没有#,例如:localhost:3000/demo/test
HashRouter
的路径 包含#,例如:localhost:3000/#/demo/test
- 刷新后对路由state参数的影响(当路由组件传递的是state参数时)
BrowserRouter
没有任何影响,因为state保存在history对象中
HashRouter
刷新后会导致路由state参数的丢失!!!!!!!!!! - 备注:
HashRouter
可以用于解决一些路径错误相关的问题(见第七个笔记)
十四、withRouter的使用
withRouter
可以加工一般组件,让一般组件具备路由组件所特有的API
withRouter
的返回值是一个新组件
下面的Header是一个一般组件,通过withRouter(Header)
可以让Header组件可以使用路由组件特有的API
class Header extends Component {
back = ()=>{
this.props.history.goBack()
}
forward = ()=>{
this.props.history.goForward()
}
render() {
console.log('Header组件收到的props是',this.props)
return (
<div className="row">
<div className="col-xs-offset-2 col-xs-8">
<div className="page-header">
<h2>React Router Demo</h2>
<button onClick={this.back}>回退</button>
<button onClick={this.forward}>前进</button>
</div>
</div>
</div>
)
}
}
export default withRouter(Header)
十五、antd的按需引入+自定主题
- 安装依赖:
yarn add react-app-rewired customize-cra babel-plugin-import less less-loader
- 修改package.json
"scripts": {
"start": "react-app-rewired start",
"build": "react-app-rewired build",
"test": "react-app-rewired test",
"eject": "react-scripts eject"
},
- 根目录下创建
config-overrides.js
//配置具体的修改规则
const { override, fixBabelImports,addLessLoader} = require('customize-cra');
module.exports = override(
fixBabelImports('import', {
libraryName: 'antd',
libraryDirectory: 'es',
style: true,
}),
addLessLoader({
//lessOptions:{
javascriptEnabled: true,
// 自定义配置主题颜色
modifyVars: { '@primary-color': 'green' },
//}
}),
);
- 备注:不用在组件里亲自引入样式了,即:import 'antd/dist/antd.css’应该删掉