React安装命令: npm i react react-dom
React基本使用
//一.引入react和react-dom的两个js文件
<script src="./node_modules/react/umd/react.development.js"></script>
<script src="./node_modules/react-dom/umd/react-dom.development.js"></script>
<div id="root"></div>
<script>
// 二.创建元素节点
// 1. 元素名称
// 2. 元素属性 传递的是个对象
// 3. 元素内容
let title = React.createElement('li', null, 'hellow react');
// 三.渲染到页面
ReactDOM.render(title, root)
</script>
React脚手架
初始化项目,命令: npx create-react-app my-pro
启动项目,在项目根目录执行命令: npm start
yarn命令简介
脚手架中使用React
import React from 'react'
import ReactDOM from 'react-dom'
let h1 = React.createElement('h1',null,'我是标题')
ReactDOM.render(h1,document.getElementById('root'))
JSX是JavaScript XML 的简写,表示在JavaScript代码中写HTML格式的代码
优势:声明式语法更加直观,与HTML结构相同,降低了学习成本,提升开发效率
使用步骤
let h1 = 我是通过JSX创建的元素
ReactDOM.render(h1,document.getElementById('root'))
为什么在脚手架中可以使用JSX语法
注意点
/>
来结束JSX语法
嵌入JS表达式
语法:{JavaScritp表达式}
例子:
const dome2 = 'likai'
const dome1 =
JSX语法{dome2}
注意:
条件渲染
根据不同的条件来渲染不同的JSX结构
let isLoading = true
let loading = ()=>{
if(isLoading){
return Loading...
}
return 加载完成
}
列表渲染
const songs = [
{ id: 1, name: 'li' },
{ id: 2, name: 'wang' },
{ id: 3, name: 'yun' }
]
const list = <ul>
{songs.map(item => <li key={item.id}>{item.name}</li>)}
</ul>
样式处理
1.行内样式 -style
在style里面我们通过对象的方式传递数据
{item.name}
这种方式比较的麻烦,不方便进行阅读,而且还会导致代码比较的繁琐
2.类名 -className
创建CSS文件编写样式代码
.container {
text-align: center
}
在js中进行引入,然后设置类名即可
import './css/index.css'
const dome1 =
JSX语法
组件创建的两种方式
1.函数创建组件
function Hellow () {
return (
<div>函数创建组件组件</div>
)
}
2.类组件
class Hellows extends React.Component {
render () {
return (
<div>
类创建组件
</div>
);
}
}
事件处理
事件绑定:
//类组件绑定事件
class Hellow extends React.Component {
render () {
return (
<div>
第一个抽离到js文件中的组件
<button onClick={this.handle}></button>
</div>
);
}
handle () {
console.log('点击了');
}
}
//函数组件绑定事件
function Hellow () {
return (
<div>函数创建组件组件 <button onClick={handle}></button></div>
)
function handle () {
console.log('点击了');
}
}
有状态组件和无状态组件
state和setState
state基本使用
class State1 extends React.Component {
// constructor(){
// super()
// //初始化state
// const state = {
// count :0
// }
// }
//上面的简化语法
state = {
count: 0
}
render () {
return (
<div>
<h1>数据展示{this.state.count}</h1>
</div>
);
}
}
setState()修改状态
class State1 extends React.Component {
// constructor(){
// super()
// //初始化state
// const state = {
// count :0
// }
// }
//上面的简化语法
state = {
count: 0
}
render () {
return (
<div>
<h1>计数器:{this.state.count}</h1>
<button onClick={() => {
this.setState({
count: this.state.count + 1
})
}}>+1</button>
</div>
);
}
}
事件绑定的this指向问题
在JSX中我们写的事件处理函数可以找到this,原因在于在JSX中我们利用箭头函数,箭头函数是不会绑定this,所以会向外一层去寻找,外层是render方法,在render方法里面的this刚好指向的是当前实例对象
1.利用箭头函数自身不绑定this的特点
class State1 extends React.Component {
state = {
count: 0
}
render () {
return (
<div>
<h1>计数器:{this.state.count}</h1>
//利用下面的箭头函数
<button onClick={() => this.hand()}>+1</button>
</div>
);
}
hand () {
this.setState({
count: this.state.count + 1
})
}
}
2.利用bind方法
class App extends React.Component {
constructor() {
super()
...
// 通过bind方法改变了当前函数中this的指向
this.onIncrement = this.onIncrement.bind(this)
}
// 事件处理程序
onIncrement() {
...
}
render() {
...
}
}
3.class的实例方法
// 事件处理程序
onIncrement = () => {
console.log('事件处理程序中的this:', this)
this.setState({
count: this.state.count + 1
})
}
受控组件
setState()
方法来修改使用步骤:
class From1 extends React.Component {
state = {
text: '',
ischeck: false
}
render () {
return (
<div>
<input type='text' valut={this.state.text} onChange={this.change} name='text' />
<input type='checkbox' checked={this.state.ischeck} onChange={this.change} name='ischeck' />
</div>
);
}
change = (e) => {
//根据类型获取值
const value = e.target.type === 'checkbox' ? e.target.checked : e.target.value
// 获取name值
const name = e.target.name
this.setState({
[name]: value
})
console.log(this.state[name]);
}
}
非受控组件
使用步骤:
React.createRef()
方法创建ref对象class From2 extends React.Component {
constructor() {
super()
//创建ref
this.txtRef = React.createRef()
}
btn = () => {
console.log(this.txtRef.current.value)
}
render () {
return (
<div>
<input type='text' ref={this.txtRef} />
<button onClick={this.btn}>获取文本框的值</button>
</div>
);
}
}
组件通讯
// 组件通讯 函数
function Dome3 (props) {
return <div>{props.name}</div>
}
ReactDOM.render(<Dome3 name='lik' />, document.getElementById('root'));
// 组件通讯 类组件
class Dome4 extends React.Component {
render () {
return (
<div>
{this.props.name}
</div>
);
}
}
ReactDOM.render(<Dome4 name='lik0' />, document.getElementById('root'));
父组件传递给子组件
class Father extends React.Component {
state = {
count: 100
}
render () {
return (
<div>
<Child name={this.state.count} />
</div>
);
}
}
function Child (props) {
return <div>父组件传递来的值:{props.name}</div>
}
ReactDOM.render(<Father name='lik0' />, document.getElementById('root'));
子组件向父组件传值
class Father1 extends React.Component {
state = {
count: 100
}
getchild = (mes) => {
console.log('接收子组件的值:' + mes);
}
render () {
return (
<div>
<Child1 getmes={this.getchild} />
</div>
);
}
}
class Child1 extends React.Component {
state = {
tet: '这是子组件传值666'
}
hand = () => {
this.props.getmes(this.state.tet)
}
render () {
return (
<div>
<button onClick={this.hand}>点我传值</button>
</div>
);
}
}
ReactDOM.render(<Father1 />, document.getElementById('root'));
兄弟组件传递
class Counter extends React.Component {
render() {
return (<div>
<Child1 />
<Child2 />
</div>
)
}
}
class Child1 extends React.Component {
render() {
return (
<h1>计数器:</h1>
)
}
}
class Child2 extends React.Component {
render() {
return (
<button>+1</button>
)
}
}
ReactDOM.render(<Counter />, document.getElementById('root'));
Context
如果出现层级比较多的情况下(例如:爷爷传递数据给孙子),我们会使用Context来进行传递
作用: 跨组件传递数据
调用 React.createContext()
创建 Provider(提供数据) 和 Consumer(消费数据) 两个组件
//创建context得到两个组件
//Provider用来提供数据 Consumer用来消费数据
const { Provider, Consumer } = React.createContext()
class context extends React.Component {
render () {
return (
<Provider value='pink'>
<div>
<Node></Node>
</div>
</Provider>
);
}
}
class Node extends React.Component {
render () {
return (
<div>
<Sub></Sub>
</div>
);
}
}
class Sub extends React.Component {
render () {
return (
<div>
<Consumer>
{
data => <span>我是子节点{data}</span>
}
</Consumer>
</div>
);
}
}
Props进阶
1.children属性
class Dome4 extends React.Component {
render () {
return (
<div>
{this.props.children}//显示内容为 子节点
</div>
);
}
}
ReactDOM.render(<Dome4 >子节点</Dome4>, document.getElementById('root'));
2.props校验
使用步骤:
prop-types (yarn add prop-types | npm i props-types)
组件名.propTypes={}
来给组件的props添加校验规则import PropTypes from 'prop-types'
const App = props => {
const arr = props.colors
const lis = arr.map((item, index) => <li key={index}>{item}</li>)
return <ul>{lis}</ul>
}
// 添加props校验
App.propTypes = {
colors: PropTypes.array
}
ReactDOM.render(
<App colors={['red', 'blue']} />,
document.getElementById('root')
)
props常见的约束规则
array、bool、func、number、object、string
element
isRequired
shape({})
import PropTypes from 'prop-types'
const App = props => {
return (
<div>
<h1>props校验:</h1>
</div>
)
}
// 添加props校验
// 属性 a 的类型: 数值(number)
// 属性 fn 的类型: 函数(func)并且为必填项
// 属性 tag 的类型: React元素(element)
// 属性 filter 的类型: 对象({area: '上海', price: 1999})
App.propTypes = {
a: PropTypes.number,
fn: PropTypes.func.isRequired,
tag: PropTypes.element,
filter: PropTypes.shape({
area: PropTypes.string,
price: PropTypes.number
})
}
ReactDOM.render(<App fn={() => {}} />, document.getElementById('root'))
props的默认值
function APP(props){
return (
<div>
此处展示props的默认值:{props.pageSize}
</div>
)
}
//设置默认值
APP.defaultProps = {
pageSize:10
}
创建时:
执行顺序:constructor() ——>render() ——>componentDidMount()
钩子函数 | 触发时机 | 作用 |
---|---|---|
constructor | 创建组件时,最先执行 | 1.初始化state 2.为事件处理程序绑定this |
render | 每次组件渲染都会触发 | 渲染UI(不能调用setState()) |
componentDidMount | 组件挂载(完成DOM渲染)后 | 1.发送网络请求 2.DOM操作 |
更新时:
执行时机:setState()、 forceUpdate()、 组件接收到新的props
说明:以上三者任意一种变化,组件就会重新渲染
执行顺序:render() ——>componentDidUpdate()
钩子函数 | 触发时机 | 作用 |
---|---|---|
render | 每次组件渲染都会触发 | 渲染UI(不能调用setState()) |
componentDidUpdate | 每次组件渲染都会触发 | 1.发送网络请求 2.DOM操作 3.如果要setState()必须放在一个if条件中 |
卸载时:
执行时机:组件从页面中消失
作用:用来做清理操作
钩子函数 | 触发时机 | 作用 |
---|---|---|
componentWillUnmount | 组件卸载(从页面消失) | 执行清理工作(比如定时器等) |
render-props模式
组件复用概述:
class Fat extends React.Component {
state = {
x: 0,
y: 0
}
hand = (e) => {
this.setState({
x: e.clientX,
y: e.clientY
})
}
componentDidMount () {
window.addEventListener('mousemove', this.hand)
}
componentWillUnmount () {
window.removeEventListener('mousemove', this.hand)
}
render () {
return this.props.children(this.state)
}
}
class Render_props extends React.Component {
render () {
return (
<div>
<Fat>
{(mouse) => { return <p>鼠标的坐标为:{mouse.x} {mouse.y}</p> }}
</Fat>
</div>
);
}
}
高阶组件
高阶组件就相当于手机壳,通过包装组件,增强组件功能
高阶组件(HOC、Higher-Order Component) 是一个函数,接收要包装的组件,返回增强后的组件
使用步骤:
//创建高阶组件
function withMouse (WrappedComponent) {
//该组件提供复用的状态逻辑
class Mouse extends React.Component {
state = {
x: 0,
y: 0
}
hand = (e) => {
this.setState({
x: e.clientX,
y: e.clientY
})
}
componentDidMount () {
window.addEventListener('mousemove', this.hand)
}
render () {
return <WrappedComponent {...this.state}></WrappedComponent>
}
}
//设置displayName
Mouse.displayName = `withMouse${getDisPlayName(WrappedComponent)}`
return Mouse
}
//解决得到的组件名称相同问题
function getDisPlayName (WrappedComponent) {
return WrappedComponent.displayName || WrappedComponent.name || 'Component'
}
//用来测试的高阶组件
class Position extends React.Component {
render () {
return (
<p>
鼠标当前位置:(x:{this.props.x},y:{this.props.y})
</p>
);
}
}
//获取增强后的组件
const MousePosition = withMouse(Position)
//测试高阶组件
class HeightComponent extends React.Component {
render () {
return (
<div>
<h1>高阶组件</h1>
<MousePosition></MousePosition>
</div>
);
}
}
displayName
displayName
,便于调试时区分不同的组件,代码如上displayName的作用:用于设置调试信息(React Developer Tools信息)
传递props
WrappedComponent
时,将state和props一起传递给组件<WrappedComponent {...this.state,...this.props}></WrappedComponent>
setState()说明
setState()
更新数据是异步的setState
不要依赖前面setState
的值setState
,只会触发一次render推荐语法:
this.setState((state, props) => {
return {
count: state.count + 1
}
}, () => {
console.log('这个回调函数会在状态更新后执行');
})
JSX语法的转化过程
createElement()
方法的语法糖(简化语法)createElement()
方法组件更新机制
组件性能优化
1.减轻state
2.避免不必要的重新渲染
//第一个参数代表props 第二个参数代表state
shouldComponentUpdate (nextProps, nextState) {
console.log(nextState, this.state);
return true//如果返回true,代表需要重新渲染,如果返回false,代表不需要重新渲染
}
纯组件
实现原理:
虚拟DOM
本质上就是一个JS对象,用来描述你希望在屏幕上看到的内容
Diff算法
基本使用:
//1.安装 npm install react-router-dom
//2.导入组件
import { BrowserRouter as Router, Route,Link } from 'react-router-dom'
//import { HashRouter as Router, Route, Link } from 'react-router-dom'
//3.使用router组件包裹整个应用
const First = () => <p>页面一的内容</p>
const Router_01 = () => (
<Router>
<div>
<h1>React路由基础</h1>
{/* 4.使用Link组件作为导航菜单(路由入口) */}
<Link to='/first'>页面一</Link>
{/* 5.使用Route组件配置路由规则和要展示的组件(路由出口) */}
<Route path='/first' component={First}></Route>
</div>
</Router>
)
常用组件说明:
路由的执行过程
编程式导航
import { BrowserRouter as Router, Route, Link } from 'react-router-dom'
class Login extends React.Component {
hand = () => {
//编程式导航跳转
this.props.history.push('/home')
}
render () {
return (
<div>
<p>登录页面</p>
<button onClick={this.hand}>登录</button>
</div>
);
}
}
const Home = (props) => {
const hand = () => {
//编程式导航后退
props.history.go(-1)
}
return (
<div><h2>我是后台首页</h2>
<button onClick={hand}>返回登录页面</button>
</div>)
}
const APP = () => (
<Router>
<div>
<h1>编程式导航</h1>
<Link to='/login'>到登录页面</Link>
<Route exact path='/login' component={Login}></Route>
<Route exact path='/home' component={Home}></Route>
</div>
</Router>
默认路由
'/'
路由模糊匹配
<Link to='/login'>登录页面</Link>
<Route path='/' component={Home}>匹配成功</Route>
路由精准匹配
技术栈
项目准备
项目目录结构
src/
assets/ 资源(图片、字体图标等)
components/ 公共组件
pages/ 页面
utils/ 工具
APP.js/ 根组件(配置路由信息)
index.css/ 全局样式
index.js/ 项目入口文件(渲染根组件、导入组件库等)
antd-mobile组件库
打开 antd-mobile的文档
antd-mobile
是 Ant Design 的移动规范的 React 实现,服务于蚂蚁及口碑无线业务。
安装
使用
// 导入组件
import { Button } from 'antd-mobile';
// 导入样式
import 'antd-mobile/dist/antd-mobile.css';
// or 'antd-mobile/dist/antd-mobile.less'
ReactDOM.render(, mountNode);
配置路由
在脚手架中使用sass
H5中利用定理定位API
地理位置API 允许用户向 Web应用程序提供他们的位置,出于隐私考虑,报告地理位置前先会请求用户许可
地理位置的API是通过 navigator.geolocation
对象提供,通过getCurrentPosition
方法获取
获取到的地理位置跟 GPS、IP地址、WIFI和蓝牙的MAC地址、GSM/CDMS的ID有关
比如:手机优先使用GPS定位,笔记本等最准确的是定位是WIFI
我们所获取到的是经纬度,其实对我们来说是没有用的,所以我们需要借助百度地图、高德地图等的开放接口,来帮我们把经纬度进行换算
navigator.geolocation.getCurrentPosition(position => {
console.log('当前位置信息', position);
//常用:latitude 维度 / longitude 经度 / heading 设备进行方向 / speed 速度
})
定位相关
百度地图API
使用步骤:
1.引入百度地图的API的JS文件,替换自己申请好的密钥
2.在index.css中设置全局样式
3.创建Map组件,配置路由,在Map组件中,创建地图容器元素,并设置样式
4.创建地图实例
5.设置中心点坐标
6.初始化地图,同时设置展示级别
<!-- 引入百度地图API的js文件 在index.html中 -->
<script type="text/javascript" src="http://api.map.baidu.com/api?v=3.0&ak=FaM624LXaYe1Xp3tECq24EjPDSad2na3"></script>
class Map extends React.Component {
componentDidMount () {
//初始化地图实例
//注意:在react脚手架中全局对象需要使用window来访问,否则会造成ESLint校验错误
const map = new window.BMap.Map("container");
//设置中心店坐标
const point = new window.BMap.Point(116.404, 39.915);
//初始化地图
map.centerAndZoom(point, 15);
}
render () {
return (
<div className='map'>
{/* 地图容器 */}
<div id='container'></div>
</div>
);
}
}
获取自身定位
//获取定位城市
const myCity = new window.BMap.LocalCity()
myCity.get(async res => {
const resule = await axios.get('http://localhost:8080/area/info?name=${res.name}')
this.setState(() => {
return {
curCityName: resule.data.body.label
}
})
})
react-virtualized
基本使用:
//导入react-virtualized组件的样式
import 'react-virtualized/styles.css'
功能处理
注意:默认情况下,只有路由 Route 直接渲染的组件才能够获取到路由信息,如果需要在其他组件中获取到路由信息可以通过 withRouter 高阶组件来获取
//导入 withRouter 高阶组件
import { withRouter } from 'react-router-dom'
function NavHeader ({ children, history, onLeftClick }) {
// 默认点击行为
const defaultHand = () => history.go(-1)
return (
<NavBar
className='navbar'
mode="light"
icon={<i className='iconfont icon-back' />}
onLeftClick={onLeftClick || defaultHand}
>{children}</NavBar>
);
}
export default withRouter(NavHeader)
CSS IN JS
CSS IN JS 是使用JavaScript 编写 CSS 的统称,用来解决CSS样式冲突,覆盖等问题;
CSS IN JS 的具体实现有50多种,比如:CSS Modules、styled-components等
推荐使用:CSS Modules(React脚手架已经集成进来了,可以直接使用)
CSS Modules
使用:
1.创建名为[name].module.css 的样式文件(React脚手架中的约定,与普通CSS区分开)
2.组件中导入样式文件**(注意语法)**
import styles from './index.module.css'
3.通过styles对象访问对象中的样式名来设置样式
<div className={styles.test}></div>
4.对于组件库中已经有的全局样式,需要使用:global() 来指定,例如:我们在修改NavBar里面文字颜色的时候,用到了一个类名叫: am-navbar-title 这个类名不是我们设置的,而是组件库中定义的,所以对于这一类,我们需要这样去设置:
:global(.am-navbar-title){color:#333}
//或者
.root :global(.am-navbar-title){......}
根据定位展示当前城市
axios优化01
配置统一的URL
axios.defaults.baseURL = 'http://localhost:8080'
// 或者
const instance = axios.create({
baseURL: 'http://localhost:8080'
})
配置生产环境和开发环境
// 通过脚手架的环境变量来解决 开发环境
在开发环境变量文件 .env.development 中,配置 REACT_APP_URL= http://localhost:8080
// 通过脚手架的环境变量解决, 生成环境
在生产环境变量文件 .env.production 中,配置 REACT_APP_URL=线上接口地址
使用环境变量
1.在项目根目录中创建文件 .env.development
2.在该文件中添加环境变量 REACT_APP_URL(注意:环境变量约定REACT_APP 开头),设置 REACT_APP_URL=http://localhost:8080
REACT_APP_URL = http://localhost:8080
3.重新启动脚手架,脚手架在运行的时候就会解析这个文件
4.在utils/url.js 中,创建 BASE_URL 变量,设置值为 process.env.REACT_APP_URL,并导出
//获取环境变量中配置的URL地址
export const BASE_URL = process.env.REACT_APP_URL
axios优化02
// 导入axios
import axios from 'axios'
//导入BASE_RUL
import { BASE_URL } from './url'
const API = axios.create({
baseURL: BASE_URL
})
export { API }
导入API,代替之前直接利用axois请求的代码
import { API } from '../../utils/api'
const res = await API.get(`/houses?cityId=${id}`)
使用WindowScroller
跟随页面滚动
WindowScroller
高阶组件,让List组件跟随页面滚动(为List组件提供状态,同时还需要设置List组件的autoHeight属性)WindowScroller
高阶组件只能提供height,无法提供width<WindowScroller>
{({ height, isScrolling, scrollTop }) => (
<AutoSizer>
{({ width }) => (
<List
autoHeight // 设置高度为 WindowScroller 最终渲染的列表高度
// 组件的宽度
width={width} // 视口宽度
// 组件的高度
height={height} // 视口高度
rowCount={this.state.count} // List列表项总条目数
// 每行的高度
rowHeight={120} // 每一行高度
rowRenderer={this.renderHouseList}
isScrolling={isScrolling}//表示是否是滚动中,用来覆盖list组件自身的滚动状态
scrollTop={scrollTop}//页面的滚动距离,用来同步list组件的滚动距离
/>
)}
</AutoSizer>
)}
</WindowScroller>
InfiniteLoader 组件
InfiniteLoader
组件,来实现无限滚动列表,从而加载更多房屋数据InfiniteLoader
文档示例,在项目中使用组件
{({ onRowsRendered, registerChild }) => (
{({ height, isScrolling, scrollTop }) => (
{({ width }) => (
)}
)}
)}
// 判断每一行数据是否加载完毕
isRowLoaded = ({ index }) => {
return !!this.state.list[index];
};
// 用来获取更多房屋列表数据
// 注意,该方法的返回值是一个 Promise 对象,并且,这个对象应该在数据加载完成时,来调用 resolve让 Promise对象的状态变为已完成
loadMoreRows = ({ startIndex, stopIndex }) => {
return new Promise(resolve => {
...
});
};
loadMoreRows = ({ startIndex, stopIndex }) => {
return new Promise(resolve => {
instance
.get("/houses", {
params: {
cityId: value,
...this.filters,
start: startIndex,
end: stopIndex
}
})
.then(res => {
this.setState({
list: [...this.state.list, ...res.data.body.list]
});
// 加载数据完成时,调用resolve即可
resolve();
});
});
};
// 渲染每一行的内容
renderHouseList = ({
key, // Unique key within array of rows
index, // 索引号
style // 重点属性:一定要给每一个行数添加该样式
}) => {
// 当前这一行的
const { list } = this.state;
const house = list[index];
// 如果不存在,需要渲染loading元素占位
if (!house) {
return (
);
}
return (
...
);
};
react-spring动画库
基本使用:
// 导入 Spring 组件
import { Spring } from 'react-spring/renderprops'
<Spring
from={{ opacity: 0 }}
to={{ opacity: 1 }}>
{props =>
<div style={props}>要实现动画的内容</div>}
</Spring>
路由参数
表单验证
formik
使用:
Login = withFormit({
mapPropsToValues:()=>({ username:'' }),//提供表单项的值
handleSubmit:(values,{props}) =>{}//提供表单提交事件
})(Login)
通过 validationSchema 配置项配合Yup来校验:
1.npm install yup (Yup 文档),导入Yup
// 导入Yup
import * as Yup from 'yup'
2.在 withFormik 中添加配置项 validationSchema,使用 Yup 添加表单校验规则
// 使用 withFormik 高阶组件包装 Login 组件,为 Login 组件提供属性和方法
Login = withFormik({
...
// 添加表单校验规则
validationSchema: Yup.object().shape({
username: Yup.string()
.required('账号为必填项')
.matches(REG_UNAME, '长度为5到8位,只能出现数字、字母、下划线'),
password: Yup.string()
.required('密码为必填项')
.matches(REG_PWD, '长度为5到12位,只能出现数字、字母、下划线')
}),
...
})(Login)
3.在 Login 组件中,通过 props 获取到 errors(错误信息)和 touched(是否访问过,注意:需要给表单元素添加 handleBlur 处理失焦点事件才生效!)
在表单元素中通过这两个对象展示表单校验错误信
在结构中需要渲染错误信息:
{/* 登录表单 */}
表单验证简化处理
1.导入 Form组件,替换form元素,去掉onSubmit
2.导入Field组件,替换input表单元素,去掉onChange,onBlur,value
3.导入 ErrorMessage 组件,替换原来的错误消息逻辑代码
4.去掉所有 props
axios拦截器
在api.js 中,添加请求拦截器 (API.interceptors.request.user())
获取到当前请求的接口路径(url)
判断接口路径,是否是以/user 开头,并且不是登录或注册接口(只给需要的接口添加请求头)
如果是,就添加请求头Authorization
// 添加请求拦截器
API.interceptors.request.use(config => {
const { url } = config
// 判断请求url路径
if (
url.startsWith('/user') &&
!url.startsWith('/user/login') &&
!url.startsWith('/user/registered')
) {
// 添加请求头
config.headers.Authorization = getToken()
}
return config
})
// 添加响应拦截器
API.interceptors.response.use(response => {
const { status } = response.data
if (status === 400) {
// 此时,说明 token 失效,直接移除 token 即可
removeToken()
}
return response
})
AuthRoute 鉴权路由组件
<AuthRoute path='/rent/add' component={Rent} />
// 官网封装的核心逻辑代码
// ...rest 把之前的组件中传递的属性原封不动传递过来
function PrivateRoute({ component: Component, ...rest }) {
return (
<Route
{...rest}
// render方法: render props模式,指定该路由要渲染的组件内容
render={props =>
// 判断是否登陆,如果登陆,跳转配置的component,如果没有登陆,利用 Redirect组件来进行重定向
fakeAuth.isAuthenticated ? (
<Component {...props} />
) : (
<Redirect
to={{
pathname: "/login",
// 把当前的页面路径保存起来,方便用户登录后能够跳回当前页面
state: { from: props.location }
}}
/>
)
}
/>
);
}
封装AuthRoute鉴权路由组件
const AuthRoute = ({ component: Component, ...rest }) => {
return (
{
const isLogin = isAuth()
if (isLogin) {
// 已登录
// 将 props 传递给组件,组件中才能获取到路由相关信息
return
} else {
// 未登录
return (
)
}
}}
/>
)
}
export default AuthRoute
登录成功后跳转
// 表单的提交事件
handleSubmit: async (values, { props }) => {
...
if (status === 200) {
// 登录成功
localStorage.setItem('hkzf_token', body.token)
/*
1 登录成功后,判断是否需要跳转到用户想要访问的页面(判断 props.location.state 是否有值)。
2 如果不需要(没有值),则直接调用 history.go(-1) 返回上一页。
3 如果需要,就跳转到 from.pathname 指定的页面(推荐使用 replace 方法模式,而不是 push)。
*/
if (!props.location.state) {
// 此时,表示是直接进入到了该页面,直接调用 go(-1) 即可
props.history.go(-1)
} else {
// replace: [home, map]
props.history.replace(props.location.state.from.pathname)
}
} else {
// 登录失败
Toast.info(description, 2, null, false)
}
}
项目打包
REACT_APP_URL = http://localhost:8080
npm install -g serve
)脚手架的配置说明
create-react-app 中隐藏了 webpack的配置,隐藏在react-scripts包中
两种方式来修改
运行命令 npm run eject 释放 webpack配置(注意:不可逆)
如果您对构建工具和配置选择不满意,您可以eject
随时进行。此命令将从项目中删除单个构建依赖项。
相反,它会将所有配置文件和传递依赖项(Webpack,Babel,ESLint等)作为依赖项复制到项目中package.json
。从技术上讲,依赖关系和开发依赖关系之间的区别对于生成静态包的前端应用程序来说是非常随意的。此外,它曾经导致某些托管平台出现问题,这些托管平台没有安装开发依赖项(因此无法在服务器上构建项目或在部署之前对其进行测试)。您可以根据需要自由重新排列依赖项package.json
。
除了eject
仍然可以使用所有命令,但它们将指向复制的脚本,以便您可以调整它们。在这一点上,你是独立的。
你不必使用eject
。策划的功能集适用于中小型部署,您不应觉得有义务使用此功能。但是,我们知道如果您准备好它时无法自定义此工具将无用
通过第三方包重写 webpack配置(比如:react-app-rewired 等)
antd-mobile 按需加载
/* package.json */
"script":{
"start": "react-app-rewired start",
"build": "react-app-rewired build",
"test": "react-app-rewired test",
"eject": "react-scripts eject"
}
在项目根目录创建文件: config-overrides.js(用于覆盖脚手架默认配置)
module.exports = function override(config,env){
return config
}
const { override, fixBabelImports } = require('customize-cra');
module.exports = override(
fixBabelImports('import', {
libraryName: 'antd-mobile',
style: 'css',
}),
);
基于路由代码分割
const CityList = React.lazy( ()=> import('./pages/CityList'))
其他性能优化
React.js 优化性能文档
react-virtualized只加载用到的组件 文档
脚手架配置 解决跨域问题
安装 http-proxy-middleware
$ npm install http-proxy-middleware --save
$ # or
$ yarn add http-proxy-middleware
创建src/setupProxy.js
并放置以下内容
const proxy = require('http-proxy-middleware');
module.exports = function(app) {
app.use(proxy('/api', { target: 'http://localhost:5000/' }));
};
**注意:**无需在任何位置导入此文件。它在启动开发服务器时自动注册,此文件仅支持Node的JavaScript语法。请务必仅使用支持的语言功能(即不支持Flow,ES模块等)。将路径传递给代理功能允许您在路径上使用通配和/或模式匹配,这比快速路由匹配更灵活