玩命加载中 . . .

React学习笔记


学习视频链接:黑马程序员

一、概述

1、什么是React

React是一个用于构建用户界面的JavaScript库。

  • 本身只处理 UI
  • 不关系路由
  • 不处理 ajax

React主要用来写HTML页面(主要用于构建UI),或构建Web应用。

如果从MVC的角度来看,React仅仅是一个视图层(V),也就是只负责视图的渲染,而并非提供了完整的M和C的功能。

React起源于Facebook的内部项目,后又用来架设Instagram的网站,并于2013年5月开源。

2、React的特点

声明(配置)式设计

只需要描述UI(HTML)看起来是什么样,就如同写HTML一般。

React负责渲染UI,并在数据变化时更新UI。

基于组件(组件化)

  • 组件时React最重要的内容
  • 组件表示页面中的部分内容
  • 组合、复用多个组件,可以实现完整的页面功能

JSX

  • 一种预编译Javascript语言,允许JavaScript和Html混搭
  • 模板中就是Javascript逻辑

单向数据流

  • 组件传值
  • 所有数据都是单向的,组件传递的数据都是单向的
  • Vue也是单向数据流
  • 没有双向数据绑定

学习一次,随处使用

  • 使用React可以开发Web应用
  • 使用React可以开发移动端原生应用(react-native)
  • 使用React可以开发VR(虚拟现实)应用(react 360)

3、React与Vue对比

技术层面

  • Vue生产力更高(更少的代码实现更强劲的功能)

  • 两个框架的效率都采用了虚拟DOM

  • Vue与React都支持组件化

  • 数据绑定

    • 都支持数据驱动视图
    • Vue支持表单控件双向数据绑定
    • React不支持双向数据绑定
  • 两者的核心库都很小,都是渐进式JavaScript库

  • React采用JSX语法来编写组件

  • Vue采用单文件组件

    • template
    • script
    • style

二、React的基本使用

1、React的安装

安装命令:

npm i react react-dom
  • react包是核心,提供创建元素、组件等功能
  • react-dom包提供DOM相关功能等

2、React的使用

1、引入react和react-dom两个js文件

<script src="...react.development.js"></script>
<script src="...react-dom.development.js"></script>

2、创建React元素

<script>
    const title = React.createElement('h1',null,'hello react')
</script>

3、渲染React元素到页面中

<div id="root"></div>
<script>
    const title = React.createElement('h1',null,'hello react')
    ReactDOM.render(title,document.getElementById('root'))
</script>

三、JSX

createElement()存在问题

  • 繁琐不简洁
  • 不直观,无法一眼看出所描述的结构
  • 不优雅,用户体验不好

JSX是JavaScript XML的简写,表示在JavaScript中写XML(HTML)格式的代码。

  • 声明式语法更加直观,与HTML结构相同
  • 降低了学习成本,提升开发效率

HTML语言直接写在JavaScript语言中,不加任何引号,这就是JSX语法。它允许HTML与JavaScript混写。

JSX是React的核心内容

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

1、JSX不是标准的ECMAScript语法,它是ECMAScript的语法扩展。

2、需要使用babel编译处理后,才能在浏览器环境中使用。

3、create-react-app脚手架中已经默认有该配置,无需手动配置。

4、编译JSX语法的包为:@babel/preset-react。

1、环境配置

  • 非模块化环境

    • babel-standalone
  • 模块化环境

    • babel-perset-react
  • Babel REPL赋值查看编译结果

2、基本语法规则

  • 必须只能有一个根节点
  • 遇到HTML标签(以<开头)就用HTML规则解析
    • 单标签不能省略结束标签
  • 遇到代码块(以{开头),就用JavaScript规则解析
  • JSX允许直接在模板中插入一个JavaScript变量
    • 如果这个变量是一个数组,则会展开这个数组的所有成员添加到模板中
  • 单标签必须结束/>

2、基本使用

使用步骤

1、使用JSX语法创建react元素。

const title = <h1>hello JSX</h1>

2、使用ReactDOM.render()方法渲染react元素到页面中。

ReactDOM.render(title,root)

注意

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

3、JSX中嵌入JavaScript

语法:{JavaScript表达式},语法中是单大括号,不是双大括号。

import React from 'react'
import ReactDOM from 'react-dom'

//JSX中使用JavaScript表达式
const content = <p>hello JSX</p>

const title = (
    <h1>
        {content}
    </h1>
)

//渲染react元素
ReactDOM.render(title,document.getElementById('root'))

注意

  • 单大括号中可以使用任意的JavaScript表达式
  • JSX自身也是js表达式
  • js中的对象是一个例外,一般只会出现在style属性中
  • 不能在{}中出现语句,例如:if/for等

4、JSX的条件渲染

条件渲染:根据条件渲染特定的JSX结构。

可以使用if/else三元运算符逻辑与运算符来实现。

// 条件渲染
//if else
const isLoading = true
const loadData = () => {
    if (isLoading) {
        return <div>loading...</div>
    }
    return <div>数据加载完成,此处显示加载后的数据</div>
}

//三元表达式
const loadDataTest = () => {
    return isLoading ? (<div>loading...</div>) : (<div>数据加载完成,此处显示加载后的数据</div>)
}

//逻辑与运算符
const loadDataT = () => {
    return isLoading && (<div>loading...</div>)
}

5、JSX的列表渲染

  • 如果药渲染一组数据,应该使用数组的map()方法

    const data = [
        {id:1,content:'test1'},
        {id:2,content:'test2'},
        {id:3,content:'test3'},
    ]
    
    const list = (
        <ul>
            {data.map(item  => <li key={item.id}>{item.content}</li>)}
        </ul>
    )

    注意

    渲染列表时应该添加key属性,key属性的值要保证唯一。尽量避免使用索引号作为key

    原则:map()遍历谁,就给谁添加key属性。

6、JSX的样式处理

行内样式 style

const title = (
    <h1 style={{color: 'red',backgroundColor: 'skyblue'}}>
        JSX的样式处理
    </h1>
)

类名 className

// 引入css
import 'css文件的相对路径'

const title = (
    <h1 className="title">
        JSX的样式处理
    </h1>
)
.title{
    text-align: center;
}

四、React组件

  • 组件表示页面中的部分功能
  • 组合多个组件实现完整的页面功能
  • 特点:可复用、独立、可组合

1、使用函数创建组件

函数组件:使用js的函数(或箭头函数)创建的组件

  • 约定1:函数名称必须以大写字母开头,React据此区分组件和普通的React的元素
  • 约定2:函数组件必须有返回值,表示该组件的结构
  • 如果返回值为null,表示不渲染任何内容
function Test() {
    return (
        <div>
            这是一个函数组件
        </div>
    )
}
  • 渲染函数组件:用函数名作为组件标签名
  • 组件标签可以是单标签也可以是双标签
function Test() {
    return (
        <div>
            这是一个函数组件
        </div>
    )
}

ReactDOM.render(<Test />,document.getElementById('root'))

2、使用类创建组件

类组件:使用ES6的class创建的组件

  • 约定1:类名称必须以大写字母开头
  • 约定2:类组件应该继承React.Component父类,从而可以使用父类中提供的方法或属性
  • 约定3:类组件必须提供render()方法
  • 约定4:render()方法必须有返回值,表示该组件的结构
class Test extends React.Component {
    render() {
        return (
            <div>
                这是一个函数组件
            </div>
        )
    }
}

ReactDOM.render(<Test />,document.getElementById('root'))

抽离为独立js文件

组件作为一个独立的个体,一般都会放到一个单独的js文件中。

1、创建js文件,例Test.js;

2、在Test.js中导入React;

3、创建组件(函数 或 类);

4、在Test.js中导出该组件;

5、在index.js中导入Test组件;

6、渲染组件。

Test.js

import React from 'react'
class Test extends React.Componment {
    render() {
        return (
            <div>
                这是一个函数组件
            </div>
        )
    }
}

// 导出Test组件
export default Test

index.js

import Test form './Test'

// 渲染导入的Test组件
ReactDOM.render(<Test />,document.getElementById('root'))

3、React事件处理

事件绑定

  • React事件绑定语法与DOM事件语法相似
  • 语法:on+事件名称={事件处理程序},例:onClick={() => {}}
  • React事件采用驼峰命名法,例:onMouseEnter、onFocus
  • 在函数组件中绑定事件
class Test extends React.Component {
    handleClick() {
        console.log('单机事件触发了')
    }

    render() {
        return (
            <button onClick={this.handleClick}>按钮</button>
        )
    }
}

事件对象

  • 可以通过事件处理程序的参数获取到事件对象
  • React中的事件对象叫做合成事件(对象)
  • 合成事件:兼容所有浏览器,无需担心跨浏览器兼容性问题
function handleClicke(e) {
    // 阻止浏览器的默认行为
    e.preventDefault()
    console.log('事件对象',e)
}

<a href="http://www.baidu.com" onClick={handleClick}>点击不会跳转页面</a>

4、有状态组件和无状态组件

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

5、state和setState

state的基本使用

  • 状态(state)即数据,是组件内部的私有数据,只能在组件内部使用
  • state的值是对象,表示一个组件中可以有多个数据
class Test extends React.Componment {
    constructor() {
        super()
        // 初始化state
        this.state = {
            count: 0
        }
    }
    render() {
        return (
            <div>有状态组件</div>
        )
    }
}

简化语法:

class Test extends React.Componment {
    state = {
        count: 0
    }
    render() {
        return (
            <div>有状态组件</div>
        )
    }
}
  • 获取状态:this.state
class Test extends React.Componment {
    state = {
        count: 0
    }
    render() {
        return (
            <div>
                <p>计数器:{this.state.count}</p>
            </div>
        )
    }
}

setState()修改状态

  • 状态是可变的
  • 语法:this.setState({需要修改的数据})
  • 不能直接修改state中的值,这是错误的
  • setState()的作用:1、修改state;2、更新UI
  • 思想:数据驱动视图
class Test extends React.Componment {
    state = {
        count: 0
    }
    render() {
        return (
            <div>
                <p>计数器:{this.state.count}</p>
                <button onClick={() => {
                    this.setState({
                        count: this.state.count + 1
                    })
                }}>+1</button>
            </div>
        )
    }
}

从JSX中抽离事件处理程序

  • JSX中参杂过多的js逻辑代码,会显得非常混乱
  • 将逻辑抽离到单独的方法中,保证JSX结构清晰
class Test extends React.Componment {
    state = {
        count: 0
    }

    // 事件处理程序
    onIncrement() {
        console.log('事件处理程序中的this:',this)
         this.setState({
             count: this.state.count + 1
         })
    }
    render() {
        return (
            <div>
                <p>计数器:{this.state.count}</p>
                <button onClick={this.onIncrement}>+1</button>
            </div>
        )
    }
}

上述会报错TypeError: Cannot read property 'setState' of undefined

  • 原因:事件处理程序中this的值为undefined
  • 希望:this指向组件实例(render方法中的this即为组件实例)

6、事件绑定this指向

箭头函数

利用箭头函数自身不绑定this的特点

class Test extends React.Component {
    state = {
        count: 0
    }

    // 事件处理程序
    onIncrement() {
        console.log('事件处理程序中的this:',this)
         this.setState({
             count: this.state.count + 1
         })
    }

    render() {
        // 箭头函数中的this指向外部环境,此处为render()方法
        return (
            <div>
                <p>计数器:{this.state.count}</p>
                <button onClick={() => this.onIncrement()}>+1</button>
            </div>
        )
    }
}

Function.prototype.bind()

利用ES5中的bind方法,将事件处理程序中的this与组件实例绑定到一起

class Test extends React.Component {
    constructor() {
        super()

        this.state = {
            count: 0
        }

        this.onIncrement = this.onIncrement.bind(this)
    }

    // 事件处理程序
    onIncrement() {
        console.log('事件处理程序中的this:',this)
         this.setState({
             count: this.state.count + 1
         })
    }

    render() {
        return (
            <div>
                <p>计数器:{this.state.count}</p>
                <button onClick={this.onIncrement}>+1</button>
            </div>
        )
    }
}

class的实例方法

利用箭头函数形式的class实例方法。

  • 该语法是实验性语法,但是由于babel的存在可以直接使用
class Test extends React.Component {
    state = {
        count: 0
    }

    // 事件处理程序
    onIncrement = () => {
        console.log('事件处理程序中的this:',this)
         this.setState({
             count: this.state.count + 1
         })
    }

    render() {
        // 箭头函数中的this指向外部环境,此处为render()方法
        return (
            <div>
                <p>计数器:{this.state.count}</p>
                <button onClick={() => this.onIncrement()}>+1</button>
            </div>
        )
    }
}

总结

推荐使用class的实例方法。

class Test extends React.Component {
    onIncrement = () => {
        this.setState({...})
    }
}

箭头函数

<button onClick={() => this.onIncrement()} />

bind方法

constructor() {
    super()
    this.onIncrement = this.onIncrement.bind(this)
}

7、表单处理

受控组件

  • HTML中的表单元素是可输入的,也就是有自己的可变状态

  • 而React中可变状态通常保存在state中,并且只能通过setState()方法来修改

  • React将state与表单元素值value绑定到一起,由state的值来控制表单元素的值

  • 受控组件:其值受到React控制的表单元素

步骤

1、在state中添加一个状态,作为表单元素的value值(控制表单元素值的来源)

2、给表单元素绑定change事件,将表单元素的值设置为state的值(控制表单元素值的变化)

class Test extends React.Component {
    state = {
        txt: ''
    }

    handleChange = e => {
        this.setState({
            txt: e.target.value
        })
    }

    render() {
        return (
            <div>
                <input type="text" value={this.state.txt} onChange={this.handleChange} />
            </div>
        )
    }
}

多表单元素优化步骤

1、给表单元素添加name属性,名称与state相同。

2、根据表单元素类型获取对应值。

3、在change事件处理程序中通过[name]来修改对应的state

class Test extends React.Component {
    state = {
        txt: '',
        content:'',
        sel:'',
        isChecked: false
    }

    handleForm = e => {
        // 获取当前DOM对象
        const target = e.target

        // 根据类型获取值
        const value = target.type === 'checkbox'
            ? target.checked
            : target.value
        // 获取name
        const name = target.name

        this.setState({
            [name]: value
        })
    }

    render() {
        return (
            <div>
                {/* 文本框 */}
                <input type="text" name="txt" value={this.state.txt} onChange={this.handleForm} />
                <br/>

                {/* 富文本框 */}
                <textarea name="content" value={this.state.content} onChange={this.handleForm}
                <br/>

                {/* 下拉框 */}
                <select name="sel" value={this.state.sel} onChange={this.handleForm}>
                    <option value="1">1</option>
                    <option value="2">2</option>
                    <option value="3">3</option>
                </select>
                <br/>

                {/* 复选框 */}
                <input type="checkbox" name="isChecked" checked={this.state.isChecked} onChange={this.handleForm}/>
            </div>
        )
    }
}

非受控组件

借助于ref,使用原生DOM方式来获取表单元素值。

ref的作用:获取DOM或组件。

使用步骤

1、调用React.createRef()方法创建一个ref对象。

constructor() {
    super()
    this.txtRef = React.createRef()
}

2、将创建好的ref对象添加到文本框中。

<input type="text" ref={this.txtRef} />

3、通过ref对象获取到文本框的值

console.log(this.txtRef.current.value)
class Test extends React.Componment {
    constructor() {
        super()

        // 创建ref
        this.txtRef = React.createRef()
    }

    // 获取文本框的值
    getTxt = () => {
        console.log('文本框值为:',this.txtRef.current.value);
    }

    render() {
        return (
            <div>
                <input type="text" ref={this.txtRef} />
                <button onClick={this.getTxt}>获取文本框的值</button>
            </div>
        )
    }
}

8、组件通讯

组件是独立且封闭的单元,默认情况下,只能使用组件自己的数据。在组件化过程中,我们将一个完整的功能拆分成多个组件,以更好的完成整个应用的功能。而在这个过程中,多个组件之间不可避免的要共享某些数据。为了实现这些功能,就需要打破组件的独立封闭性,让其与外界沟通。这个过程就是组件通讯。

9、组件的props

组件是封闭的,要接收外部数据应该通过props来实现。

props的作用:接收传递给组件的数据。

  • 传递数据:给组件标签添加属性。
  • 接收数据:函数组件通过参数props接收数据,类组件通过this.props接收数据。
<Test id={1} name="test" title="test" />

function Test(props) {
    console.log(props)
    return (
        <div>接收数据:{props.title}</div>
    )
}
class Test extends React.Component {
    render() {
        return (
            <div>接收到的数据:{this.props.title}</div>
        )
    }
}

<Test id={1} name="test" title="test" />

特点

1、可以给组件传递任意类型的数据;

const Test = props => {
    console.log('props:',props)
    props.fn()

    return (
        <div>
            <p>props:{props.title}</p>
            {props.tag}
        </div>
    )
}

ReactDOM.render(
    <Test
        id={1}
        title="test"
        colors={['red','green','blue']}
        fn={() => console.log('这是一个函数')}
        tag={<p>这是一个p标签</p>}
    />,
    document.getElementById('root')
)

2、props是只读的对象,只能读取属性的值,无法修改对象;

3、使用类组件时,如果写了构造函数,应该将props传递给super(),否则,无法在构造函数中获取到props。

class Test extends React.Component {
    constructor(props) {
        // 推荐将props传递给父类构造函数
        super(props)
    }

    render() {
        return <div>接收到的数据:{this.props.title}</div>
    }
}

10、组件通讯的三种方式

组件之间的通讯分为3钟:

  • 父组件->子组件
  • 子组件->父组件
  • 兄弟组件

父组件传递数据给子组件

1、父组件提供传递的state数据;

2、给子组件标签添加属性,值为state中的数据;

3、子组件中通过props接收父组件中传递的数据。

class Parent extends React.Component {
    state = { title: 'test' }
    render() {
        return (
            <div className="parent">
                父组件:
                <Child name={this.state.title} />
            </div>
        )
    }
}

const Child = props => {
    console.log('子组件:',props)
    return (
        <div className="child">
            <p>子组件,接收到父组件的数据:{props.name}</p>
        </div>
    )
}

子组件传递数据给父组件

利用回调函数,父组件提供回调,子组件调用,将要传递的数据作为回调函数的参数。

1、父组件提供一个回调函数(用于接收数据);

2、将该函数作为属性的值,传递给子组件;

3、子组件通过props调用回调函数;

4、将子组件的数据作为参数传递给回调函数。

class Parent extends React.Component {
    state = {
        parentMsg: ''
    }
    getChildMsg = msg => {
        console.log('接收到子组件数据:',msg)

        this.setState({
            parentMsg: msg
        })
    }
    render() {
        return (
            <div className="parent">
                父组件:{this.state.parentMsg}
                <Child getMsg={this.getChildMsg}/>
            </div>
        )
    }
}

class Child extends React.Component {
    state = {
        msg: 'React'
    }
    handleClick = () => {
        this.props.getMsg(this.state.msg)
    }
    return (
        <div className="child">
            子组件:{''}
            <button onClick={this.handleClick>点击,给父组件传递数据</button>
        </div>
    )
}

注意:回调函数中this指向问题。

兄弟组件

  • 将共享状态提升到最近的公共组件中,由公共父组件来管理这个状态
  • 公共父组件职责:
    • 1、提供共享状态
    • 2、提供操作共享状态的方法
  • 要通讯的子组件只需通过props接收状态或操作状态的方法
class Counter extends React.Component {
    //提供共享状态
    state = {
        count: 0
    }
    //提供修改状态的方法
    onIncrement = () => {
        this.setState({
            count: this.state.count + 1
        })
    }
    render() {
        return (
            <div>
                <Child1 count={this.state.count}/>
                <Child2 onIncrement={this.onIncrement}/>
            </div>
        )
    }

    const Child1 = props => {
        return <h3>计数器:{props.count}</h3>
    }

    const Child2 = (props) => {
        return <button onClick={() => props.onIncrement()}>+1</button>
    }
}

11、Context

作用:跨组件传递数据(如主题、语言等)

使用步骤

1、调用React.createContext()创建Provider(提供数据)和Consumer(消费数据)两个组件。

const { Provider, Consumer } = React.createContext()

2、使用Provider组件作为父节点。

<Provider>
    <div className="App">
        <Child1 />
    </div>
</Provider>

3、设置value属性,表示要传递的数据。

<Provider value="red">

4、调用Consumer组件接收数据。

<Consumer>
    {data => <span>data参数表示接收到的数据 -- {data}</span>}
</Consumer>
// 创建context得到两个组件
const { Provider, Consumer } = React.createContext()

class App extends React.Component {
    render() {
        return (
            <Provider value="red">
                <div className="app">
                    <Node />
                </div>
            </Provider>
        )
    }
}

const Node = props => {
    return (
        <div className="node">
            <SubNode />
        </div>
    )
}

const SubNode = props => {
    return (
        <div class="subnode">
            <Child /> 
        </div>
    )
}

const Child = props => {
    return (
        <div className="child">
            <Consumer>{data => <span>子节点 -- {data} </sapn>}</Consumer>
        </div>
    )
}

ReactDOM.render(<App />,document.getElementById('root'))

12、props深入

children属性

children属性:表示组件标签的子节点。当组件标签有子节点时,props就会有该属性。

  • children属性与普通的props一样,值可以是任意值(文本、React元素、组件,甚至是函数)

children为文本节点:

const App = props => {
    return (
        <div>
            <h2>组件标签的子节点:</h1>
            {props.children}
        </div>
    )
}

ReactDOM.render(<App>这是子节点</App>,document.getElementById('root'))

children为React元素:

const App = props => {
    console.log(props)
    return (
        <div>
            <h2>组件标签的子节点:</h2>
            {props.children}
        </div>
    )
}

ReactDOM.rendre(
    <App>
        <p>这是一个子节点,是一个p标签</p>
    </App>,
    document.getElementById('root')
)

children为组件:

const Test = () => <button>button组件</button>

const App = props => {
    console.log(props)
    return (
        <div>
            <h2>组件标签的子节点:</h2>
            {props.children}
        </div>
    )
}

ReactDOM.rendre(
    <App>
        <Test />
    </App>,
    document.getElementById('root')
)

children为函数:

const App = props => {
    console.log(props)
    props.children()

    return (
        <div>
            <h2>组件标签的子节点:</h2>
        </div>
    )
}

ReactDOM.render(
    <App>{() => console.log('这是一个函数子节点')}</App>,
    document.getElementById('root')
)

props校验

  • 对于组件而言,props是外来的,无法保证组件使用者传入什么格式的数据
  • 如果传入的数据格式不对,可能导致组件内部报错

关键问题:组件的使用者不知道明确的错误原因。

props校验:允许在创建时,就指定props的类型、格式等。(作用:捕获使用组件时因为props导致的错误,给出明确的错误提示,增加组件的健壮性。)

使用步骤:

1、安装包prop-types(yarn add prop-types或npm i props-types);

2、导入prop-types包;

3、使用组件名.propTypes = {}来给组件的props添加校验规则;

4、校验规则通过PropTypes对象来指定。

import React from 'react'
import ReactDOM from 'react-dom'
import PropTypes form 'prop-types'

const App = props => {
    const arr = props.colors
    const lists = arr.map((item,index) => <li key={index}>{item}</li>)

    return <ul>{lists}</ul>
}

// 添加props校验
App.propTypes = {
    // 约定colors属性为array类型
    // 如果类型不对,则报出明确错误,便于分析错误原因
    colors: PropTypes.array
}

ReactDOM.render(<App colors={['green','red','blue']} />,document.getElementById('root'))

约束规则

1、常见类型:arrayboolfuncnumberobjectstring

2、React元素类型:element

3、必填项:isRequired

4、特定结构的对象:shape({})

// 常见类型
optionalFunc: PropTypes.func,
// 必选
requiredFunc: PropTypes.func.isRequired,
// 特定结构的对象
optionalObjectWithShape: PropTypes.shape({
    color: PropTypes.string,
    fontSize: PropTypes.number
})

props的默认值

作用:给props设置默认值,在未传入props时生效。

const App = props => {
    console.log(props)
    return (
        <div>
            <h2>此处展示props的默认值:{props.pageSize}</h2>
        </div>
    )
}

// 添加props默认值
App.defaultProps = {
    pageSize: 10
}

ReactDOM.render(<App />,document.getElementById('root'))

13、组件的生命周期

React生命周期图

组件的生命周期概述

  • 意义:组件的生命周期有助于理解组件的运行方式、完成更复杂的组件功能、分析组件错误原因等。
  • 组件的生命周期:组件从被创建到挂载到页面中运行,再到组件不用时卸载的过程。
  • 生命周期的每个阶段总是伴随着一些方法调用,这些方法就是生命周期的钩子函数。
  • 钩子函数的作用:为开发人员在不同阶段操作组件提供了时机。
  • 只有类组件才有生命周期。

生命周期的三个阶段

  • 创建时(挂载阶段)

    • 执行时机:组件创建时(页面加载时)
    • 执行顺序:constructor()->render()->componentDidMount
    钩子函数 触发时机 作用
    constructor 创建组件时,最先执行 1、初始化state
    2、为事件处理程序绑定this
    render 每次组件渲染都会触发 渲染UI 注意:不能调用setState()
    componentDidMount 组件挂载(完成DOM渲染)后 1、发送网络请求
    2、DOM操作
    class App extends React.Component {
        constructor(props) {
            super(props)
    
            // 初始化state
            this.state = {
                count: 0
            }
    
            console.warn('生命周期钩子函数:constructor')
        }
        componentDidMount() {
            const title = document.getElementById('title')
            console.log(title)
            console.warn('生命周期钩子函数:componentDidMount')
        }
        render() {
            console.warn('生命周期钩子函数:render')
    
            return (
                <div>
                <h2 id="title">count:</h2>
                <button id="btn">+1</button>
                </div>
            )
        }
    }
  • 更新时(更新阶段)

    • 执行时机:1、setState();2、forceUpdate();3、组件接收到新的props。
    • 以上三者任意一种变化,组件就会重新渲染。
    • 执行顺序:render()->componentDidUpdate()
    钩子函数 触发时机 作用
    render 每次组件渲染都会触发 渲染UI(与 挂载阶段 是同一个render)
    componentDidUpdate 组件更新(完成DOM渲染)后 1、发送网络请求
    2、DOM操作
    注:如果要setState()必须放在一个if条件中
    class App extends React.Component {
        constructor(props) {
            super(props)
    
            // 初始化state
            this.state = {
                count: 0
            }
    
            console.warn('生命周期钩子函数:constructor')
        }
    
        handleClick = () =>{
            this.setState({
                count: this.state.count+1
            })
    
            //强制更新
            //this.forceUpdate()
        }
    
        render() {
            console.warn('生命周期钩子函数:render')
    
            return (
                <div>
                    <Child count={this.state.count} />
                    <button id="btn" onClick={this.handleClick}>+1</button>
                </div>
            )
        }
    }
    
    class Child extends React.Component {
        render() {
            console.warn('Child组件:render')
            return <h2 id="title">count:{this.props.count}</h2>
        }
    
        //如果要调用 setState() 更新状态,必须要放在一个if条件中
        //如果直接调用 setState() 更新状态,会导致递归更新
        componentDidUpdate() {
            console.warn('Child组件:componentDidUpdate')
    
            //比较更新前后的props是否相同,来决定是否重新渲染组件
            console.log('上一次props:',prevProps,',现在的props:',this.props)
    
            if(prevProps.count !== this.props.count){
                this.setState({})
    
                //发送ajax请求
                //......
            }
    
            //获取DOM
            const title = document.getElementById('title')
            console.log(title.innerHTML)
        }
    }
    
    ReactDOM.render(<App />,document.getElementById('root'))
  • 卸载时(卸载阶段)

    • 执行时机:组件从页面中消失

      钩子函数 触发时机 作用
      componentWillUnmount 组件卸载(从页面中消失) 执行清理工作(如:清理定时器等)
      class App extends React.Component {
          constructor(props) {
              super(props)
      
              // 初始化state
              this.state = {
                  count: 0
              }
      
              console.warn('生命周期钩子函数:constructor')
          }
      
          handleClick = () =>{
              this.setState({
                  count: this.state.count+1
              })
          }
      
          render() {
              return (
                  <div>
                      {this.state.count > 5 ?(
                           <p>不能再加了...</p>
                       ) :(
                          <Child count={this.state.count} />
                      )}
                      <button id="btn" onClick={this.handleClick}>+1</button>
                  </div>
              )
          }
      }
      
      class Child extends React.Component {
          componentDidMount() {
              // 开启定时器
              this.timerId = setIntterval(() => {
                  console.log('定时器正在执行')
              },500)
          }
          render() {
              console.warn('Child组件:render')
              return <h2 id="title">count:{this.props.count}</h2>
          }
      
          componentWillUnmount() {
              console.warn('生命周期函数:componentWillUnmount')
      
              // 清理定时器
              clearInterval(this.timerId) 
          }
      }
      
      ReactDOM.render(<App />,document.getElementById('root'))

不常用的钩子函数

  • getDerivedStateFromProps
  • shouldComponentUpdate
  • getSnapshotBeforeUpdate

14、render-props和高阶组件

render组件复用

如果两个组件中的部分功能相似或相同,可以复用相似或相同的功能。

复用什么?1、state;2、操作state的方法(组件状态逻辑)。

组件复用的两种方式:1、render props模式;2、高阶组件(HOC)。

render props模式

思路:将要复用的state和操作state的方法封装到一个组件中。

那怎样拿到该组件中复用的state?

在使用组件时,添加一个值为函数的prop,通过函数参数来获取(需要组件内部实现)

如何渲染任意UI?

使用该函数的返回值作为要渲染的UI内容(需要组件内部实现)

使用步骤:

1、创建组件,在组件中提供复用的状态逻辑代码(1、状态;2、操作状态的方法);

2、将要复用的状态作为props.render(state)方法的参数,暴露到组件外部.

// 导入图片资源
import img form './images/test.jpg'

// 创建Mouse组件
class Mouse extends React.Component {
    // 鼠标位置
    state = {
        x: 0,
        y: 0
    }

    // 鼠标移动事件的事件处理程序
    handleMouseMove = e => {
        this.setState({
            x: e.clientX,
            y: e.clientY
        })
    }

    // 监听鼠标移动事件
    componentDidMount() {
        window.addEventListener('mousemove',this.handleMouseMove)
    }

    render() {
        return this.props.render(this.state)
    }
}

class App extends React.Component {
    render() {
        return (
            <div>
                <h2>render props模式</h2>
                <Mouse
                    render={mouse => {
                        return (
                            <p>
                                鼠标位置:x={mouse.x} y={mouse.y}
                            </p>
                        )
                    }}
                />
                {/*  */}
                <Mouse 
                    render={mouse => {
                        return (
                            <img 
                                src={img} 
                                alt="test" 
                                style={{
                                  postition:'absolute',
                                  top: mouse.y,
                                  left: mouse.x
                                }} />)       
                }}></Mouse>
            </div>
        )
    }
}

ReactDOM.render(<App />,document.getElementById('root'))

children代替render属性

注意:并不是该模式教render props就必须使用名为render的prop,实际上可以使用任意名称的prop。

把prop是一个函数并且到苏组件要渲染什么内容的技术叫做:render props模式。

推荐使用children代替render属性。

// 创建Mouse组件
class Mouse extends React.Component {
    // 鼠标位置
    state = {
        x: 0,
        y: 0
    }

    // 鼠标移动事件的事件处理程序
    handleMouseMove = e => {
        this.setState({
            x: e.clientX,
            y: e.clientY
        })
    }

    // 监听鼠标移动事件
    componentDidMount() {
        window.addEventListener('mousemove',this.handleMouseMove)
    }

    render() {
        return this.props.children(this.state)
    }
}

class App extends React.Component {
    render() {
        return (
            <div>
                <h2>render props模式</h2>
                <Mouse
                    {mouse => {
                        return (
                            <p>
                                鼠标位置:x={mouse.x} y={mouse.y}
                            </p>
                        )
                    }}
                />
            </div>
        )
    }
}

ReactDOM.render(<App />,document.getElementById('root'))

代码优化

1、给render props模式添加props校验;

2、在组件卸载时接触mousemove事件绑定。

// 创建Mouse组件
class Mouse extends React.Component {
    // 鼠标位置
    state = {
        x: 0,
        y: 0
    }

    // 鼠标移动事件的事件处理程序
    handleMouseMove = e => {
        this.setState({
            x: e.clientX,
            y: e.clientY
        })
    }

    // 监听鼠标移动事件
    componentDidMount() {
        window.addEventListener('mousemove',this.handleMouseMove)
    }

    // 组件卸载时移除事件绑定
    componentWillUnmount() {
        window.removeEventListener('mousemove',this.handleMouseMove)
    }

    render() {
        return this.props.children(this.state)
    }
}

// 添加props校验
Mouse.propTypes = {
    // 需导入包 import PropTypes from 'prop-types'
    children: PropTypes.func.isRequired
}

class App extends React.Component {
    render() {
        return (
            <div>
                <h2>render props模式</h2>
                <Mouse
                    {mouse => {
                        return (
                            <p>
                                鼠标位置:x={mouse.x} y={mouse.y}
                            </p>
                        )
                    }}
                />
            </div>
        )
    }
}

ReactDOM.render(<App />,document.getElementById('root'))

高阶组件

采用包装(装饰)模式,实现状态逻辑复用。

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

高阶组件内部创建一个类组件,在这个类组件中提供复用的状态逻辑代码,通过prop将复用的状态传递给被包装组件WrappedComponent。

使用步骤

1、创建一个函数,名称约定以with开头;

2、指定函数参数,参数应该以大写字母开头(作为要渲染的组件);

3、在函数内部创建一个类组件,提供复用的状态逻辑代码,并返回;

4、在该组件中,渲染参数组件,同时将状态通过prop传递给参数组件;

5、调用该高阶组件,传入要增强的组件,通过返回值拿到增强后的组件,并将其渲染到页面中。

// 创建高阶组件
function withMouse(WrappedComponent) {
    // 该组件提供复用的状态逻辑
    class Mouse extends React.Component {
        // 鼠标状态
        state = {
            x: 0,
            y: 0
        }

        handleMouseMove = e => {
            this.setState({
                x: e.clientX,
                y: e.clientY
            })
        }

        // 控制鼠标状态的逻辑
        componentDidMount() {
            window.addEventListener('mousemove',this.handleMouseMove)
        }

        componentWillUnmount() {
            window.removeEventListener('mousemove',this.handleMouseMove)
        }

        render() {
            return <WrappedComponent {...this.state}></WrappedComponent>
        }
    }
    return Mouse
}
// 测试高阶组件
const Position = props => {
    <p>
        鼠标当前位置:(x:{props.x},y:{props.y})
    </p>
}

// 获取增强后的组件
const MousePosition = withMouse(Position)

class App extends React.Component {
    render() {
        return (
            <div>
                <h2>高阶组件</h2>
                {/* 渲染增强后的组件 */}
                <MousePosition />
            </div>
        )
    }
}

ReactDOM.render(<App />,document.getElementById('root'))

设置displayName

使用高阶组件存在的问题:默认情况下,React使用组件名称作为displayName,这样会导致得到的组件名称相同。

解决方式:为高阶组件设置displayName,便于调试时区分不同的组件。

displayName的作用:用于设置调试信息(React Developer Tools信息)。

// 导入图片资源
import img form './images/test.jpg'

// 创建高阶组件
function withMouse(WrappedComponent) {
    // 该组件提供复用的状态逻辑
    class Mouse extends React.Component {
        // 鼠标状态
        state = {
            x: 0,
            y: 0
        }

        handleMouseMove = e => {
            this.setState({
                x: e.clientX,
                y: e.clientY
            })
        }

        // 控制鼠标状态的逻辑
        componentDidMount() {
            window.addEventListener('mousemove',this.handleMouseMove)
        }

        componentWillUnmount() {
            window.removeEventListener('mousemove',this.handleMouseMove)
        }

        render() {
            return <WrappedComponent {...this.state}></WrappedComponent>
        }
    }

    // 设置displayName
    Mouse.displayName = `WithMouse${getDisplayName(WrappedComponent)}`
    return Mouse
}

function getDisplayName(WrappedComponent) {
    return WrappedComponent.displayName || WrappedComponent.name || 'Component';
}


// 测试高阶组件
const Position = props => {
    <p>
        鼠标当前位置:(x:{props.x},y:{props.y})
    </p>
}

const Test = props => {
    <img 
        src={img} 
        alt="test" 
        style={{
               postition:'absolute',
               top: mouse.y,
               left: mouse.x
              }} />
}

// 获取增强后的组件
const MousePosition = withMouse(Position)

const MouseTest = withMose(Test)

class App extends React.Component {
    render() {
        return (
            <div>
                <h2>高阶组件</h2>
                {/* 渲染增强后的组件 */}
                <MousePosition />
                <MouseTest />
            </div>
        )
    }
}

ReactDOM.render(<App />,document.getElementById('root'))

传递props

问题:props丢失,因为高阶组件没有往下传递props。

解决方式:渲染WrappedComponent时,将state和this.props一起传递给组件。

传递方式:<WrappedComponent {...this.state} {...this.props} />

// 创建高阶组件
function withMouse(WrappedComponent) {
    // 该组件提供复用的状态逻辑
    class Mouse extends React.Component {
        // 鼠标状态
        state = {
            x: 0,
            y: 0
        }

        handleMouseMove = e => {
            this.setState({
                x: e.clientX,
                y: e.clientY
            })
        }

        // 控制鼠标状态的逻辑
        componentDidMount() {
            window.addEventListener('mousemove',this.handleMouseMove)
        }

        componentWillUnmount() {
            window.removeEventListener('mousemove',this.handleMouseMove)
        }

        render() {
            return <WrappedComponent {...this.state} {...this.props}></WrappedComponent>
        }
    }

    // 设置displayName
    Mouse.displayName = `WithMouse${getDisplayName(WrappedComponent)}`
    return Mouse
}

function getDisplayName(WrappedComponent) {
    return WrappedComponent.displayName || WrappedComponent.name || 'Component';
}


// 测试高阶组件
const Position = props => {
    <p>
        鼠标当前位置:(x:{props.x},y:{props.y})
    </p>
}

// 获取增强后的组件
const MousePosition = withMouse(Position)

class App extends React.Component {
    render() {
        return (
            <div>
                <h2>高阶组件</h2>
                {/* 渲染增强后的组件 */}
                <MousePosition title="position"/>
            </div>
        )
    }
}

ReactDOM.render(<App />,document.getElementById('root'))

五、React原理

1、setState()的说明

更新数据

  • setState()是异步更新数据的
  • 可以多次调用setState(),只会触发一次更新渲染

使用该语法时,后面的setState()不要依赖于前面的setState()。

class App extends React.Component {
    state = {
        count: 0
    }
    handleClick = () => {
        this.setState({
            count: this.state.count + 1
        })
        console.log('count:',this.state.count)  //0
        this.setState({
            count: this.state.count + 1 //0+1
        })
        console.log('count:',this.state.count)  //0
    }
    // ......
}

推荐语法

使用setState((state,props) => {})语法

  • 参数state:表示最新的state
  • 参数props:表示最新的props
class App extends React.Component {
    state = {
        count: 0
    }
    handleClick = () => {
        this.setState((state,props) => {
            return {
                count: state.count+1 //0+1
            }
        })

        this.setState((state,props) => {
            console.log('count:',state.count) //1 后输出
            return {
                count: state.count+1 //1+1
            }
        })
        console.log('count:',this.state.count) //0 先输出
    }
    // ......
}

第二个参数

场景:在状态更新(页面完成重新渲染)后立即执行某个参数。

语法:setState(updater[, callback])

class App extends React.Component {
    state = {
        count: 0
    }
    handleClick = () => {
        this.setState((state,props) => {
            return {
                count: state.count+1 //0+1
            }
        },
        // 状态更新后并且重新渲染后,立即执行
        () => {
            console.log('状态更新完成:',this.state.count)
            console.log(document.getElementById('title').innerText)
            document.title = '更新state后的标题:'+this.state.count
        })
    }


    render() {
        return (
            <div>
                <h2 id="title">计数器:{this.state.count}</h2>
                <button onClick={this.handleClick}>+1</button>
            </div>
        )
    }
}

2、JSX语法的转换过程

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

3、组件更新机制

setState()的两个作用:

  • 修改state
  • 更新组件(UI)

过程:父组件重新渲染时,也会重新渲染子组件。但只会渲染当前组件子树(当前组件及其所有子组件)。

4、组件性能优化

减轻state

只存储跟组件渲染相关的数据(比如:count/列表数据/loading等)

  • 不用做渲染的数据不要放在state中,比如定时器id等
  • 对于需要在多个方法中用到的数据,应该放在this中

避免不必要的重新渲染

组件更新机制:父组件更新会引起子组件也被更新。子组件没有任何变化时也会重新渲染,那如何避免不必要的重新渲染呢?

解决方式:使用钩子函数shouldComponentUpdate(nextProps,nextState),通过返回值决定该组件是否重新渲染,返回true表示重新渲染,false表示不重新渲染。

触发时机:更新阶段的钩子函数,组件重新渲染前执行(shouldComponentUpdate->render)

class App extends React.Component {
    state = {
        count: 0
    }
    handleClick = () => {
        this.setState((state,props) => {
            return {
                count: state.count+1 //0+1
            }
        })
    }

    // 钩子函数 nextProps,nextState表示最新的参数和状态
    shouldComponentUpdate() {
        // 更新前的状态
        console.log('更新前的状态:',this.state)

        // 返回false,阻止组件重新渲染
        return false
    }

    render() {
        console.log('组件更新了')
        return (
            <div>
                <h2 id="title">计数器:{this.state.count}</h2>
                <button onClick={this.handleClick}>+1</button>
            </div>
        )
    }
}

生成随机数:

nextState

class App extends React.Component {
    state = {
        number: 0
    }

    handleClick = () => {
        this.setState(() => {
            return (
                number: Math.floor(Math.random() * 3)
            )
        })
    }

    // 因为两次生成的随机数可能相同,如果相同,此时,不需要重新渲染
    shouldComponentUpdate(nextState) {
        console.log('最新状态:',nextState,',当前状态:',this.state)
        return nextState.number !== this.state.number
    }

    render() {
        console.log('render')
        return (
            <div>
                <h2>随机数:{this.statte.number}</h2>
                <button onClick={this.handleClick}>重新生成</button>
            </div>
        )
    }
}

nextProps

class App extends React.Component {
    state = {
        number: 0
    }

    handleClick = () => {
        this.setState(() => {
            return (
                number: Math.floor(Math.random() * 3)
            )
        })
    }

    render() {
        console.log('父组件中的render')
        return (
            <div>
                <NumberBox number={this.state.number} />
                <button onClick={this.handleClick}>重新生成</button>
            </div>
        )
    }
}

class NumberBox extends React.Component {
    // 因为两次生成的随机数可能相同,如果相同,此时,不需要重新渲染
    shouldComponentUpdate(nextProps) {
        console.log('最新props:',nextProps,',当前props:',this.props)
        return nextProps.number !== this.props.number
    }

    render() {
        console.log('子组件中的render')
        return (<h2>随机数:{this.statte.number}</h2>)
    }
}

纯组件

纯组件:PureComponent与React.Component功能相似。但PureComponent内部自动实现了shouldComponentUpdate钩子,不需要手动比较。

原理:纯组件内部通过分别对比前后两次props和state的值,来决定是否重新渲染组件。

说明:纯组件内部的对比是shallow compare(浅层对比)。

  • 对于值类型来说:比较两个值是否相同(直接赋值即可)
  • 对于引用类型来说:只比较对象的引用(地址)是否相同

state或props中属性值为引用类型时,应该创建新数据,不要直接修改原数据。

class App extends React.Component {
    state = {
        obj: {
            number: 0
        }
    }

    handleClick = () => {
        // 正确做法
        const newObj = {...this.state.obj,number:Math.floor(Math.random()*3)}
        this.setState(() => {
            return{
                obj: newObj
            }
        })
        // 错误做法
        /* const newObj = this.state.obj
        newObj.number = Math.floor(Math.random()*3)
        this.setState(() => {
            return (
                obj: newObj
            )
        }) */
    }

    render() {
        console.log('父组件中的render')
        return (
            <div>
                <NumberBox number={this.state.obj.number} />
                <button onClick={this.handleClick}>重新生成</button>
            </div>
        )
    }
}

class NumberBox extends React.Component {
    // 因为两次生成的随机数可能相同,如果相同,此时,不需要重新渲染
    shouldComponentUpdate(nextProps) {
        console.log('最新props:',nextProps,',当前props:',this.props)
        return nextProps.number !== this.props.number
    }

    render() {
        console.log('子组件中的render')
        return (<h2>随机数:{this.statte.number}</h2>)
    }
}

5、虚拟DOM和Diff算法

React更新视图的思想:只要state变化就重新渲染视图。

问题:组件中只有一个DOM元素需要更新时,也得把整个组件的内容重新渲染到页面中?

理想状态:部分更新,只更新变化的地方。

虚拟DOM:本质上就是一个JS对象,用来描述你希望在屏幕上看到的内容(UI)。

执行过程

1、初次渲染时,React会根据初始state(Model),创建一个虚拟DOM对象(树)。

2、根据虚拟DOM生成真正的DOM,渲染到页面中。

3、当数据变化后(setState()),重新根据新的数据,创建新的虚拟DOM对象(树)。

4、与上一次得到的虚拟DOM对象,使用Diff算法对比(找不同),得到需要更新的内容。

5、最终,React只将变化的内容更新(patch)到DOM中,重新渲染到页面。

组件render()调用之后,根据状态和JSX结构生成虚拟DOM对象。

  • render方法的调用并不意味着浏览器中的重新渲染
  • render方法调用仅仅说明要进行diff算法

六、React路由

1、使用步骤

1、安装:

yarn add react-router-dom

2、导入路由的三个核心组件:Router/Route/Link

import { BrowserRouter as Router,Route,Link } from 'react-router-dom'

3、使用Router组件包裹整个应用

4、使用Link组件作为导航菜单(路由入口)

5、使用Route组件配置路由规则和展示的组件(路由出口)

import React from 'react'
import ReactDOM from 'react-dom'
import { BrowserRouter as Router, Route, Link } from 'react-router-dom'

const Test = () => <p>Test页面的内容</p>

const App = () => (
    <Router>
        <div>
            <h2>React路由基础</h2>
            {/* 指定路由入口 */}
            <Link to="/test">Test</Link>

            {/* 指定路由出口 */}
            <Route path="/test" component={Test}></Route>
        </div>
    </Router>
)

ReactDOM.render(<App />,document.getElementById('root'))

2、常用组件说明

  • Router组件:包裹整个应用,一个React应用只需要使用一次

  • 两种常用Router:HashRouter和BrowserRouter

  • HashRouter:使用URL的哈希值实现(localhost:8080/#/test)

  • BorwserRouter:使用H5的history API实现(localhost:8080/test)

  • Link组件:用于指定导航链接(标签)

    // to属性:浏览器地址栏总的pathname(localhost.pathname)
    <Link to="/test">Test</Link>
  • Route组件:指定路由展示组件相关信息

    // path属性:路由规则
    // component属性:展示的组件
    // Route组件写在哪,渲染出来的组件就在哪显示
    <Route path="/test" component={Test}></Route>

3、路由的执行过程

1、点击Link组件(a标签),修改了浏览器地址栏中的url。

2、React路由监听到地址栏url的变化。

3、React路由内部遍历所有Route组件,使用路由规则(path)与pathname进行匹配。

4、当路由规则(path)能够匹配地址栏中的pathname时,就显示该Route组件的内容。

4、编程式导航

通过JS代码来实现页面的跳转。

this.props.history.push('/test')
  • history是React路由提供的,用于获取浏览器历史记录的相关信息。
  • push(path):跳转到某个页面,参数path表示要跳转的路径。
  • go(n):前进或后退到某个页面,参数n表示前进或后退页面数量(比如:-1表示后退到上一页)
import React from 'react'
import ReactDOM from 'react-dom'
import { BrowserRouter as Router, Route, Link } from 'react-router-dom'

class Login extends React.Component{
    handleLogin = () => {
        // 使用编程式导航实现路由跳转
        this.props.history.push('/home')
    }
    render() {
        return (
            <div>
                <p>登录界面:</p>
                <button onClick={this.handleLogin}>登录</button>
            </div>
        )
    }
}

const Home = props => {
    const handleBack = () => {
        // go(-1)表示返回上一个页面
        props.history.go(-1)
    }
    return (
        <div>
            <h2>首页界面</h2>
            <button onClick={handleBack}>返回登录页面</button>
        </div>
    )
}

const App = () => (
    <Router>
        <div>
            <h2>Test</h2>
            <Link to="/login">跳转到登录页面</Link>
            <Route path="/login" component={Login}></Route>
            <Route path="/home" component={Home}></Route>
        </div>
    </Router>
)

ReactDOM.render(<App />,document.getElementById('root'))

5、默认路由

表示进入页面时就会匹配的路由。

默认路由path为/

<Route path="/" component={Home} />

6、匹配模式

模糊匹配

当Link组件的to属性值为“/login”时,为什么默认路由也被匹配成功?

import React from 'react'
import ReactDOM from 'react-dom'
import { BrowserRouter as Router, Route, Link } from 'react-router-dom'

const Home = () => <p>首页,进入页面就可以看到</p>

const Login = () => <p>Login登录页面</p>

const App = () => (
    <Router>
        <div>
            <h2>Test</h2>
            <Link to="/login">跳转到登录页面</Link>

            <Route path="/" component={Home}></Route>
            <Route path="/login" component={Login}></Route>
        </div>
    </Router>
)

ReactDOM.render(<App />,document.getElementById('root'))

默认情况下,React路由时模糊匹配模式。

模糊匹配规则:只要pathname以path开头就会匹配成功。

path 能够匹配的pathname
/ 所有pathname
/test /test或/test/a或/test/a/b

精确匹配

给Route组件添加exact属性,让其变为精确匹配模式。

精确匹配:只有当path和pathname完全匹配时才会展示该路由。

import React from 'react'
import ReactDOM from 'react-dom'
import { BrowserRouter as Router, Route, Link } from 'react-router-dom'

const Home = () => <p>首页,进入页面就可以看到</p>

const Login = () => <p>Login登录页面</p>

const App = () => (
    <Router>
        <div>
            <h2>Test</h2>
            <ul>
                <li>
                    <Link to="/">首页</Link>
                </li>
                <li>
                    <Link to="/login">跳转到登录页面</Link>
                </li>
            </ul>


            <Route exact path="/" component={Home}></Route>
            <Route path="/login" component={Login}></Route>
        </div>
    </Router>
)

ReactDOM.render(<App />,document.getElementById('root'))

推荐:给默认路由添加exact属性。


  目录