React基础

react

1.React基本用法、脚手架、JSX

React安装命令: npm i react react-dom

  • react 包是核心,提供创建元素,组件等功能
  • react-dom 包提供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

    • npx 目的:提升包内提供的命令行工具的使用体验
    • 原来:先安装脚手架包,再使用这个包中提供的命令
    • 现在:无需安装脚手架包,就可以直接使用这个包提供的命令
    • create-react-app 这个是脚手架名称 不能随意更改
    • my-pro 自己定义的项目名称
  • 启动项目,在项目根目录执行命令: npm start

    yarn命令简介

    • yarn 是Facebook发布的包管理器,可以看做是npm的替代品,功能与npm相同
    • yarn具有快速,可靠和安全的特点
    • 初始化新项目:yarn init
    • 安装包: yarn add 包名称
    • 安装项目依赖: yarn

脚手架中使用React

  • 导入react和react-dom两个包
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结构相同,降低了学习成本,提升开发效率

使用步骤

  • 使用JSX语法创建react元素
let h1 = <h1>我是通过JSX创建的元素</h1>
  • 使用ReactDOM来渲染元素
ReactDOM.render(h1,document.getElementById('root'))

为什么在脚手架中可以使用JSX语法

  • JSX 不是标准的ECMAScript语法,它是ECMAScript的语法拓展
  • 需要使用babel编译处理后,才能在浏览器环境中使用
  • create-react-app脚手架中已经默认有该配置,无需手动配置
  • 编译JSX语法的包: @bable/preset-react

注意点

  • React元素的属性名使用驼峰命名法
  • 特殊属性名:class -> className,for -> htmlFor,tabindex -> tabIndex
  • 如果没有子节点的React元素可以用 /> 来结束
  • 推荐:使用 小括号包裹JSX,从而避免JS中自动插入分号报错

JSX语法

嵌入JS表达式

语法:{JavaScritp表达式}

例子:

const dome2 = 'likai'
const dome1 = <h1>
  JSX语法{dome2}
</h1>

注意:

  • 只要是合法的js表达式都可以进行嵌入
  • JSX自身也是js表达式
  • 注意:js中的对象是一个例外,一般只会出现在style属性中
  • 注意:在{}中不能出现语句

条件渲染

根据不同的条件来渲染不同的JSX结构

let isLoading = true
let loading = ()=>{
    if(isLoading){
        return <div>Loading...</div>
    }
    return <div>加载完成</div>
}

列表渲染

  • 如果需要渲染一组数据,我们应该使用数组的 map () 方法
  • 注意:渲染列表的时候需要添加key属性,key属性的值要保证唯一
  • 原则:map()遍历谁,就给谁添加key属性
  • 注意:尽量避免使用索引号作为key
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里面我们通过对象的方式传递数据

<li key={item.id} style={{'color': 'red',"backgroundColor": 'pink'}}>{item.name}</li>

这种方式比较的麻烦,不方便进行阅读,而且还会导致代码比较的繁琐

2.类名 -className

创建CSS文件编写样式代码

.container {
    text-align: center
}

在js中进行引入,然后设置类名即可

import './css/index.css'
const dome1 = <h1 className='title'>
  JSX语法
</h1>

2.组件、事件处理、state和setState、this指向问题

组件创建的两种方式

1.函数创建组件

  • **约定1:**函数名称必须以大写字母开头
  • **约定2:**函数组件必须有返回值,表示该组件的结构
  • 如果返回值为null,表示不渲染任何内容
function Hellow () {
  return (
    <div>函数创建组件组件</div>
  )
}

2.类组件

  • 使用ES6语法的class创建的组件
  • 约定1:类名称也必须要大写字母开头
  • 约定2:类组件应该继承React.Component父类,从而可以使用父类中提供的方法或者属性
  • 约定3:类组件必须提供 render 方法
  • 约定4:render方法中必须要有return返回值
class Hellows extends React.Component {
  render () {
    return (
      <div>
        类创建组件
      </div>
    );
  }
}

事件处理

事件绑定:

  • React事件绑定语法与DOM事件语法相似
  • 语法:on+事件名称=事件处理函数,比如 onClick = function(){}
  • 注意:React事件采用驼峰命名法
//类组件绑定事件
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('点击了');
  }
}
  • 在React中绑定事件与原生很类似
  • 需要注意点在于,在React绑定事件需要遵循驼峰命名法
  • 类组件与函数组件绑定事件是差不多的,只是在类组件中绑定事件函数的时候需要用到this,代表指向当前的类的引用,在函数中不需要调用this

有状态组件和无状态组件

  • 函数组件又叫做 无状态组件,类组件又叫做 有状态组件
  • 状态(state) 即数据
  • 函数组件没有自己的状态,只负责数据展示
  • 类组件有自己的状态,负责更新UI,让页面动起来

state和setState

state基本使用

  • 状态(state)即数据,是组件内部的私有数据,只能在组件内部使用
  • state的值是对象,表示一个组件中可以有多个数据
  • 通过this.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()修改状态

  • 状态是可变的
  • 语法:this.setState({要修改的数据})
  • 注意:不要直接修改state中的值,这是错误的
  • setState() 作用:1.修改 state 2.更新UI
  • 思想:数据驱动视图
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的实例方法

  • 利用箭头函数形式的class实例方法
  • 注意:该语法是实验性语法,但是,由于babel的存在可以使用
  // 事件处理程序
  onIncrement = () => {
    console.log('事件处理程序中的this:', this)
    this.setState({
      count: this.state.count + 1
    })
  }

3.表单处理、组件通讯、Context、props进阶

受控组件

  • HTML中的表单元素是可输入的,也就是有自己的可变状态
  • 而React中可变状态通常保存在state中,并且只能通过setState() 方法来修改
  • React讲state与表单元素值value绑定在一起,有state的值来控制表单元素的值
  • 受控组件:值受到react控制的表单元素

使用步骤:

  • 在state中添加一个状态,作为表单元素的value值
  • 给表单元素绑定change事件,将表单元素的值设置为state的值
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]);
  }
}

非受控组件

  • 说明:借助于ref,使用元素DOM方式获取表单元素值
  • ref的作用:获取DOM或者组件

使用步骤:

  • 调用 React.createRef() 方法创建ref对象
  • 将创建好的 ref 对象添加到文本框中
  • 通过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'));
  • 可以给组件传递任意类型的数据
  • props是只读属性,不能对值进行修改
  • 注意:使用类组件时,如果类组件写了构造函数,应该将props传递给super(),否则,无法在构造函数中获取到props,其他的地方是可以拿到的

父组件传递给子组件

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'));

兄弟组件传递

  • 将共享状态(数据)提升到最近的公共父组件中,由公共父组件管理这个状态
  • 这个称为状态提升
  • 公共父组件职责:1. 提供共享状态 2.提供操作共享状态的方法
  • 要通讯的子组件只需要通过props接收状态或操作状态的方法
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属性

  • children属性: 表示组件标签的子节点,当组件标签有子节点时,props就会有该属性
  • children属性与普通的props一样,值可以使任意值(文本、react元素、组件、甚至是函数)
class Dome4 extends React.Component {
  render () {
    return (
      <div>
        {this.props.children}//显示内容为 子节点
      </div>
    );
  }
}
ReactDOM.render(<Dome4 >子节点</Dome4>, document.getElementById('root'));

2.props校验

  • 对于组件来说,props是外来的,无法保证组件使用者传入什么格式的数据,简单来说就是组件调用者可能不知道组件封装着需要什么样的数据
  • 如果传入的数据不对,可能会导致报错
  • 关键问题:组件的使用者不知道需要传递什么样的数据
  • props校验:允许在创建组件的时候,指定props的类型、格式等

使用步骤:

  • 安装包 prop-types (yarn add prop-types | npm i props-types)
  • 导入prop-types 包
  • 使用组件名.propTypes={} 来给组件的props添加校验规则
  • 校验规则通过PropTypes对象来指定
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
  • React元素类型: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
}

4.生命周期、render-props、高阶组件

创建时:

执行顺序: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模式

组件复用概述:

  • 复用什么?
    • state
    • 操作state的方法
  • 两种方式:
    • render props模式
    • 高阶组件(HOC)
  • 注意: 这两种方式不是新的API,而是利用React自身特点的编码技巧,演化而成的固定模式
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>
    );
  }
}
  • 注意:并不是该模式叫 render props就必须使用名为render的prop,实际上可以使用任意名称的prop
  • 把prop是一个函数并且告诉组件要渲染什么内容的技术叫做: render props模式
  • 推荐:使用childre代替render属性

高阶组件

高阶组件就相当于手机壳,通过包装组件,增强组件功能

高阶组件(HOC、Higher-Order Component) 是一个函数,接收要包装的组件,返回增强后的组件

使用步骤:

  • 创建一个函数,名称约定以with开头
  • 指定函数参数,参数应该以大写字母开头
  • 在函数内部创建一个类组件,提供复用的状态逻辑代码,并返回
  • 在该组件中,渲染参数组件,同时将状态通过prop传递给参数组件
  • 调用该高阶组件,传入要增强的组件,通过返回值拿到增强后的组件,并将其渲染到页面
//创建高阶组件
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>
    );
  }
}
  • 使用高阶组件存在的问题:得到两个组件的名称相同
  • 原因:默认情况下,React使用组件名称作为displayName
  • 解决方式:为高阶组件设置displayName,便于调试时区分不同的组件,代码如上
  • displayName的作用:用于设置调试信息(React Developer Tools信息)

传递props

  • 问题:如果没有传递props,会导致props丢失问题
  • 解决方式: 渲染WrappedComponent时,将state和props一起传递给组件
<WrappedComponent {...this.state,...this.props}></WrappedComponent>

5.React原理

setState()说明

  • setState()更新数据是异步的
  • 注意:使用该语法,后面的setState不要依赖前面setState的值
  • 多次调用setState,只会触发一次render

推荐语法:

this.setState((state, props) => {
      return {
        count: state.count + 1
      }
    }, () => {
      console.log('这个回调函数会在状态更新后执行');
    })

JSX语法的转化过程

  • JSX仅仅是createElement() 方法的语法糖(简化语法)
  • JSX语法被 @babel/preset-react 插件编译为createElement() 方法
  • React 元素: 是一个对象,用来描述你希望在屏幕上看到的内容

组件更新机制

  • setState() 的两个作用
    • 修改state
    • 更新组件
  • 过程:父组件重新渲染时,也会重新渲染子组件,但只会渲染当前组件子树(当前组件以其所有子组件)

组件性能优化

1.减轻state

  • 减轻state:只存储跟组件渲染相关的数据(比如:count/ 列表数据 /loading等)
  • 注意:不用做渲染的数据不要放在state中
  • 对于这种需要在多个方法中用到的数据,应该放到this中(比如定时器的id名等)

2.避免不必要的重新渲染

  • 组件更新机制:父组件更新会引起子组件也被更新,这种思路很清晰
  • 问题:子组件没有任何变化时也会重新渲染
  • 如果避免不必要的重新渲染?
  • 解决方式:使用钩子函数 shouldComponentUpdate(nextProps, nextState)
    • 在这个函数中,nextProps和nextState是最新的状态以及属性
  • 作用:这个函数有返回值,如果返回true,代表需要重新渲染,如果返回false,代表不需要重新渲染
  • 触发时机:更新阶段的钩子函数,组件重新渲染前执行(shouldComponentUpdate => render)
//第一个参数代表props 第二个参数代表state
  shouldComponentUpdate (nextProps, nextState) {
    console.log(nextState, this.state);
    return true//如果返回true,代表需要重新渲染,如果返回false,代表不需要重新渲染
  }

纯组件

  • 纯组件: PureComponent 与 React.Component 功能相似
  • 区别: PureComponent 内部自动实现了 shouldComponentUpdate钩子,不需要手动比较
  • 原理:纯组件内部通过分别比对前后两次 props和state的值,来决定是否重新渲染组件

实现原理:

  • 说明:纯组件内部的对比是 shallow compare(浅层对比)
  • 对于值类型来说:比较两个值是否相同
  • 引用类型:只比对对象的引用地址是否相同
  • 注意:state 或 props 中属性值为引用类型时,应该创建新数据,不要直接修改原数据

虚拟DOM

本质上就是一个JS对象,用来描述你希望在屏幕上看到的内容

Diff算法

  • 初次渲染时,React会根据初始化的state(model),创建一个虚拟DOM对象(树)
  • 根据虚拟DOM生成真正的DOM,渲染到页面
  • 当数据变化后(setState()),会重新根据新的数据,创建新的虚拟DOM对象(树)
  • 与上一次得到的虚拟DOM对象,使用Diff算法比对(找不同),得到需要更新的内容
  • 最终,React只将变化的内容更新(patch)到DOM中,重新渲染到页面

6.路由

基本使用:

//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>
)

常用组件说明:

  • **Router组件:**包裹整个应用,一个React应用只需要使用一次
    • 两种常用的Router: HashRouter和BrowserRouter
    • HashRouter: 使用URL的哈希值实现 (localhost:3000/#/first)
    • 推荐 BrowserRouter:使用H5的history API实现(localhost3000/first)
  • **Link组件:**用于指定导航链接(a标签)
    • 最终Link会编译成a标签,而to属性会被编译成 a标签的href属性
  • **Route组件:**指定路由展示组件相关信息
    • path属性:路由规则,这里需要跟Link组件里面to属性的值一致
    • component属性:展示的组件
    • Route写在哪,渲染出来的组件就在哪

路由的执行过程

  • 当我们点击Link组件的时候,修改了浏览器地址栏中的url
  • React路由监听地址栏url的变化
  • React路由内部遍历所有的Route组件,拿着Route里面path规则与pathname进行匹配
  • 当路由规则(path)能够匹配地址栏中的pathname时,就展示该Route组件的内容

编程式导航

  • **编程式导航:**通过JS代码来实现页面跳转
  • history是React路由提供的,用于获取浏览器历史记录的相关信息
  • **push(path):**跳转到某个页面,参数path表示要跳转的路径
  • go(n):前进或后退功能,参数n表示前进或后退页面数量
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>

默认路由

  • 现在的路由都是通过点击导航菜单后展示的,如果进入页面的时候就主动触发路由呢
  • 默认路由:表示进入页面时就会匹配的路由
  • 默认路由:只需要把path设置为 '/'

路由模糊匹配

  • 默认情况下,React路由是模糊匹配模式
  • 模糊匹配规则:只要pathname以path开头就会匹配成功
<Link to='/login'>登录页面</Link>
<Route path='/' component={Home}>匹配成功</Route>

路由精准匹配

  • 给Route组件添加exact属性,让其变为精准匹配模式
  • 精确匹配:只有当path和pathname完全匹配时才会展示改路由

7.项目概述

技术栈

  • React核心库:react、react-dom、react-router-dom
  • 脚手架:create-react-app
  • 数据请求:axios
  • UI组件库:antd-mobile
  • 其他组件库:react-virtualized、formik+yup、react-spring等
  • 百度地图API

项目准备

  • 本地接口部署
    • 创建并导入数据:数据库名称
  • 使用脚手架初始化项目
    • 使用 npx create-react-app 项目名
    • 进入到项目根目录 使用 npm start

项目目录结构

src/
	assets/		    资源(图片、字体图标等)
	components/		公共组件
	pages/		    页面
	utils/		    工具
	APP.js/			根组件(配置路由信息)
	index.css/		全局样式
	index.js/		项目入口文件(渲染根组件、导入组件库等)

antd-mobile组件库

  • 打开 antd-mobile的文档

  • antd-mobileAnt Design 的移动规范的 React 实现,服务于蚂蚁及口碑无线业务。

  • 安装

    • npm install antd-mobile --save
  • 使用

    • 导入组件
    • 导入样式
    // 导入组件
    import { Button } from 'antd-mobile';
    // 导入样式
    import 'antd-mobile/dist/antd-mobile.css';  
    // or 'antd-mobile/dist/antd-mobile.less'
    ReactDOM.render(<Button>Start</Button>, mountNode);
    

配置路由

  • 安装 npm i react-router-dom
  • 导入路由组件:Router / Route / Link
  • 在pages文件夹中创建 Home/index.js 和 CityList/index.js 两个组件
  • 使用Route组件配置首页和城市选择页面

在脚手架中使用sass

  • 打开脚手架文档,找到添加Sass样式
  • 安装Sass: npm i node-sass --save
  • 创建后缀名为.scss 或者 .sass 的样式文件
  • 在组件中导入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

  • H5的地理位置API只能获取到对应经纬度信息
  • 实际开发中,会使用百度地图/高德地图来完成地理位置的相关功能
  • 租房项目中,通过百度地图API实现地理位置和地图找房功能
  • 我们需要去参照百度地图文档
  • 注册百度开发者账号,申请对应的AK

使用步骤:

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 是React组件,用来高效渲染大型列表和表格数据
  • GitHub地址: react-virtualized

基本使用:

  • 安装: npm i react-virtualized
  • 在项目入口文件 index.js 中导入样式文件
//导入react-virtualized组件的样式
import 'react-virtualized/styles.css'
  • 打开 文档, 点击List组件,进入List的文档中

功能处理

注意:默认情况下,只有路由 Route 直接渲染的组件才能够获取到路由信息,如果需要在其他组件中获取到路由信息可以通过 withRouter 高阶组件来获取

  • 从 react-router-dom 中导入 withRouter 高阶组件
  • 使用 withRouter 高阶组件包装 NavHeader 组件
    • 目的:包装后,就可以在组建中获取到当前路由信息
  • 从 props 中就能获取history对象
  • 调用history对象的 go() 方法就能实现返回上一页功能了
//导入 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

  • CSS Modules 通过对CSS类名重命名,保证每一个类名的唯一性,从而避免样式冲突问题
  • 实现方式:webpack的css-loader 插件
  • 命名采用:BEM(Block块、Element元素、Modifier三部分组成)命名规范。比如: .list_item_active
  • 在React脚手架中演化成:文件名、类名、hash(随机)三部分,只需要指定类名即可

使用:

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){......}

根据定位展示当前城市

  • 获取当前定位城市
  • 使用 地址解析器 解析当前城市坐标
  • 调用 centerAndZoom() 方法在地图中展示当前城市,并设置缩放级别为11
  • 在地图中添加比例尺和平移缩放控件

axios优化01

  • 能够通过 axios.create() 方法来构建axios实例对象,并且配置baseURL
  • 能够知道 .env.development 和 .env.production 两个文件的作用
  • 能够配置开发环境变量
  • 能够在代码中引入配置的环境变量的值

配置统一的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

  • 在utils/api.js 中,导入 axios和BASE_URL
  • 调用 axios.create() 方法创建一个axios实例
  • 给 create 方法,添加配置baseURL,值为 BASE_URL
  • 导出API对象
// 导入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 跟随页面滚动

  • **默认:**List组件只让组件自身出现滚动条,无法让整个页面滚动,也就无法实现标题吸顶功能
  • **解决方式:**使用WindowScroller高阶组件,让List组件跟随页面滚动(为List组件提供状态,同时还需要设置List组件的autoHeight属性)
  • 注意:WindowScroller高阶组件只能提供height,无法提供width
  • **解决方式:**在WindowScroller组件中使用AutoSizer高阶组件来为List组件提供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 文档示例,在项目中使用组件
<InfiniteLoader
  isRowLoaded={this.isRowLoaded}//表示每一行数据是否加载完成
  loadMoreRows={this.loadMoreRows}//加载更多数据的方法,在需要加载更多数据时会调用该方法
  rowCount={count}//列表数据总条数
>
  {({ onRowsRendered, registerChild }) => (
    <WindowScroller>
      {({ height, isScrolling, scrollTop }) => (
        <AutoSizer>
          {({ width }) => (
            <List
              onRowsRendered={onRowsRendered}
              ref={registerChild}
              autoHeight // 设置高度为 WindowScroller 最终渲染的列表高度
              // 组件的宽度
              width={width} // 视口宽度
              // 组件的高度
              height={height} // 视口高度
              rowCount={count} // List列表项总条目数
              // 每行的高度
              rowHeight={120} // 每一行高度
              rowRenderer={this.renderHouseList}
              isScrolling={isScrolling}
              scrollTop={scrollTop}
            />
          )}
        </AutoSizer>
      )}
    </WindowScroller>
  )}
</InfiniteLoader>
 // 判断每一行数据是否加载完毕
  isRowLoaded = ({ index }) => {
    return !!this.state.list[index];
  };
  // 用来获取更多房屋列表数据
  // 注意,该方法的返回值是一个 Promise 对象,并且,这个对象应该在数据加载完成时,来调用 resolve让 Promise对象的状态变为已完成
  loadMoreRows = ({ startIndex, stopIndex }) => {
    return new Promise(resolve => {
       ...
    });
  };
  • 在loadMoreRows方法中,根据起始索引和结束索引,发送请求,获取更多房屋数据
  • 获取到最新的数据后,与当前list中的数据合并,再更新state,并调用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方法中,判断house是否存在
  • 不存在的时候,就渲染一个loading元素
  • 存在的时候,再渲染HouseItem组件
// 渲染每一行的内容
  renderHouseList = ({
    key, // Unique key within array of rows
    index, // 索引号
    style // 重点属性:一定要给每一个行数添加该样式
  }) => {
    // 当前这一行的
    const { list } = this.state;
    const house = list[index];
    // 如果不存在,需要渲染loading元素占位
    if (!house) {
      return (
        <div key={key} style={style}>
          <p className={styles.loading}></p>
        </div>
      );
    }
    return (
      ...
    );
  };

react-spring动画库

  • 场景:展示筛选对话框的时候,实现动画效果,增强用户体验
  • react-spring是基于spring-physics(弹簧物理)的react动画库,动画效果更加流畅、自然
  • 优势:
    • 几乎可以实现任意UI动画效果
    • 组件式使用方式(render-props模式),简单易用,符合react的声明式特性,性能高
  • github地址
  • 官方文档

基本使用:

  • 安装:npm install react-spring
  • 打开Spring组件文档
  • 导入Spring文档,使用Spring组件包裹要实现动画效果的遮罩层div
// 导入 Spring 组件
import { Spring } from 'react-spring/renderprops'
  • 通过render-props模式,讲参数props设置为遮罩层div的style
  • 给Spring组件添加from属性,指定:组件第一次渲染时的动画状态
  • 给Spring组件添加to属性,指定:组件要更新的新动画状态
  • props就是透明度有0~1中变化的值
<Spring 
from={{ opacity: 0 }} 
to={{ opacity: 1 }}>
        {props =>  
         <div style={props}>要实现动画的内容</div>}
</Spring>

路由参数

  • 使用 一个 路由规则匹配不同的URL路径,同时I获取到URL中不同的内容,利用路由参数来解决
  • 让一个路由规则,同时匹配多个符合该规则的URL路径
  • 语法:/detail/:id ,其中:id 就是路由参数
  • 获取页面路径参数的id值:this.props.match.params

表单验证

formik

  • Github地址:formik文档
  • 场景:表单处理,表单验证
  • 优势:轻松处理React中的复杂表单,包括:获取表单元素的值,表单验证和错误信息,处理表单提交,并且将这些内容放在一起统一处理,有利于代码阅读,重构,测试等
  • 使用两种方式:1. 高阶组件(withFormik) 2. render-props(<Formik render={() => {}} />)

使用:

  • 安装: npm install formik
  • 导入 withFormik,使用withFormit 高阶组件包裹Login组件
  • 为withFormit提供配置对象: mapPropsToValues / handleSubmit
  • 在Login组件中,通过props获取到values(表单元素值对象),handleSubmit,handleChange
  • 使用values提供的值,设置为表单元素的value,使用handleChange设置为表单元素的onChange
  • 使用handleSubmit设置为表单的onSubmit
  • 在handleSubmit中,通过values获取到表单元素值
  • 在handleSubmit中,完成登录逻辑
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 处理失焦点事件才生效!)

在表单元素中通过这两个对象展示表单校验错误信

在结构中需要渲染错误信息:

{/* 登录表单 */}
<WingBlank>
  <form onSubmit={handleSubmit}>
    ...  用户名的错误提示
    {errors.username && touched.username && (
      <div className={styles.error}>{errors.username}</div>
    )}
    ... 密码框的错误提示
    {errors.password && touched.password && (
      <div className={styles.error}>{errors.password}</div>
    )}
    ...
</WingBlank>

表单验证简化处理

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())
  • 判断返回值中的状态码
  • 如果是400,标示token超时或异常,直接移除token
// 添加响应拦截器
API.interceptors.response.use(response => {
  const { status } = response.data
  if (status === 400) {
    // 此时,说明 token 失效,直接移除 token 即可
    removeToken()
  }
  return response
})

AuthRoute 鉴权路由组件

  • 限制某个页面只能在登陆的情况下访问,但是在React路由中并没有直接提供该组件,需要手动封装,来实现登陆访问控制(类似与Vue路由的导航守卫)
  • 参数 react-router-dom的鉴权文档
  • AuthRoute 组件实际上就是对原来Route组件做了一次包装,来实现一些额外的功能
<AuthRoute path='/rent/add' component={Rent} />
  • render方法:render props模式,指定该路由要渲染的组件内容
  • Redirect组件:重定向组件,通过to属性,指定要跳转的路由信息
// 官网封装的核心逻辑代码
// ...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鉴权路由组件

  • 在components目录中创建AuthRoute/index.js 文件
  • 创建组件AuthRoute并导出
  • 在AuthRoute组件中返回Route组件(在Route基础上做了一层包装,用于实现自定义功能)
  • 给Route组件,添加render方法,指定改组件要渲染的内容(类似与component属性)
  • 在render方法中,调用isAuth() 判断是否登陆
  • 如果登陆了,就渲染当前组件(通过参数component获取到要渲染的组件,需要重命名)
  • 如果没有登陆,就重定向到登陆页面,并且指定登陆成功后腰跳转的页面路径
  • 将AuthRoute组件接收到的props原样传递给Route组件(保证与Route组件使用方式相同)
  • 使用AuthRoute组件配置路由规则,验证是否实现页面的登陆访问控制
const AuthRoute = ({ component: Component, ...rest }) => {
  return (
    <Route
      {...rest}
      render={props => {
        const isLogin = isAuth()

        if (isLogin) {
          // 已登录
          // 将 props 传递给组件,组件中才能获取到路由相关信息
          return <Component {...props} />
        } else {
          // 未登录
          return (
            <Redirect
              to={{
                pathname: '/login',
                state: {
                  from: props.location
                }
              }}
            />
          )
        }
      }}
    />
  )
}
export default AuthRoute

登录成功后跳转

  • 登陆成功后,判断是否需要跳转到用户想要访问的页面(判断props.location.state 是否有值)
  • 如果不需要,则直接调用history.go(-1) 返回上一页
  • 如果需要,就跳转到from.pathname 指定的页面(推荐使用replace方法模式,不是push)
 // 表单的提交事件
  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)
    }
  }

项目打包

  • 打开 create-react-app 脚手架的 打包文档说明
  • 在根目录创建 .env.production 文件,配置生产环境的接口基础路径
REACT_APP_URL = http://localhost:8080
  • 在项目根目录中,打开终端
  • 输入命令: npm run build,进行项目打包,生成build文件夹(打包好的项目内容)
  • 将build目录中的文件内容,部署到都服务器中即可
  • 也可以通过终端中的提示,使用 serve-s build 来本地查看(需要全局安装工具包 serve 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 按需加载

  • 打开 antd-mobile 在create-react-app中的使用文档
  • 安装 npm install react-app-rewired customize-cra(用于脚手架重写配置)
  • 修改package.json 中的 scripts
/* 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
}
  • 安装 npm install babel-plugin-import 插件(用于按需加载组件代码和样式)
  • 修改 config-overrides.js 文件,配置按需加载功能
const { override, fixBabelImports } = require('customize-cra');
module.exports = override(
  fixBabelImports('import', {
    libraryName: 'antd-mobile',
    style: 'css',
  }),
);
  • 重启项目(npm start)
  • 移除index.js 中导入的 antd-mobile样式文件
  • 将index.css 移动到App后面,让index.css 中的页面背景生效

基于路由代码分割

  • 目的:将代码按照路由进行分割,只在访问该路由的时候才加载该组件内容,提高首屏加载速度
  • 如何实现? React.lazy() 方法 + import() 方法、Suspense组件(React Code-Splitting文档
  • React.lazy() 作用: 处理动态导入的组件,让其像普通组件一样使用
  • import(‘组件路径’),作用:告诉webpack,这是一个代码分割点,进行代码分割
  • Suspense组件:用来在动态组件加载完成之前,显示一些loading内容,需要包裹动态组件内容
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模块等)。将路径传递给代理功能允许您在路径上使用通配和/或模式匹配,这比快速路由匹配更灵活

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值