Bootstrap

React

1. 什么是React

React是一个用于构建用户界面的JavaScript库。用户界面:HTML页面(前端)

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

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

2. React的特点

1. 声明式

我们只需要描述UI(HTML)看起来是什么样,就跟写HTML一样。

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

const jsx= <div className=”app”>

<h1>Hellp React!动态数据变化:{count}</h1>

</div>

2. 基于组件

组件表示页面中的部分内容,组合,复用多个组件,可以实现完整的页面功能。

3. 学习一次,随时使用

使用React可以开发Web应用

使用React可以开发移动端原生应用(react-native)

使用React可以开发VR应用(react360)

3. React的基本使用

1. React的安装

npm i react react-dom

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

3. 引入react和react-dom两个js文件

4. 创建React元素

<script>
    //参数一:元素名称
    //参数二:元素属性
    //参数三及其以后的参数:元素的子节点
    const title=React.createElement(‘h1’,null,’Hello React’)

    //渲染React元素到页面中
    //参数一:要渲染的react元素
    //参数二:挂载点
    ReactDOM.render(title,document.getElementById(‘root’))
</script>

4. React脚手架的使用

1. 脚手架是开发现代Web应用的必备

现代的Web开发应用已经不再是像传统的那种方式→你只要去创建一个页面,然后在页面中去写HTML,写CSS,写JS,然后最终就把功能实现了。

注意:现代的Web应用已经不再是这样子了。

现在的Web应用,其实写代码只是其中要考虑的一环,我们还要考虑到像代码的规范呀,最终项目打包上线呀,编译工具等等。

我们是需要一套完整的解决方案,来去帮我们开发项目的。

2. 充分利用Webpack,Babel,ESLint等工具辅助项目开发

3. 零配置,无需手动配置繁琐的工具即可使用。

初始化命令

npx create-react-app my-app

启动项目,在项目根目录执行命令:

npm start

5. npx命令

npm v5.2.0引入的一条命令。

目的:提升包内提供的命令行工具的使用体验

原来:先安装脚手架包,再使用这个包中提供的命令

现在:无需安装脚手架包,就可以直接使用这个包提供的命令。

推荐使用:npx create-react-app my-app

npm init react-app my-app

yarn create react-app my-app

在脚手架中使用React

1. 导入react和react-dom两个包

(不再通过script标签导入了,而是通过ES6里面的模块化语法导入)

import React from ‘react’

import ReactDOM from ‘react-dom’

2. 调用React.createElement()方法创建react元素

3. 调用ReactDOM.render()方法渲染react元素到页面中

6. JSX

1. createElement()的缺点

繁琐不简洁,不直观,无法一眼看出所描述的结构

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

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

2. JSX的使用步骤

1. 使用JSX语法创建react元素

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

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

ReactDOM.render(title,root)

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

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

(默认情况下,在一个普通的HTML页面中去写JSX语法,是会报错的)

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

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

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

3. JSX使用注意点

1. React元素的属性名使用驼峰命名法

2. 特殊属性名:class -->className,for ->htmlFor,tabindex -->tabIndex

3. 没有子节点的React元素可以使用/>结束

4. 推荐:使用小括号包裹JSX,从而避免JS中的自动插入分号陷阱

//使用小括号包裹JSX

const div=(

    <div>Hello JSX</div>

)

4. 在JSX中使用JavaScript表达式

为什么要嵌入JS表达式

数据存储在JS中

语法:{JavaScript表达式}

注意:语法中是单大括号,不是双大括号!

const name=’Jack’

const dv=(
    <div>你好,我叫:???</div>
)

const name=’Jack’

const dv=(
    <div>你好,我叫:{name}</div>
)

JSX中使用JavaScript表达式的注意点

1. 单大括号中可以使用任意的JavaScript表达式。

2. JSX自身也是JS表达式

const h1=<h1>我是JSX</h1>

const dv={

    <div>嵌入表达式:{h1}</div>

}

1. 注意:JS中的对象是一个例外,一般只会出现在style属性中

2. 注意:不能在{}中出现语句(比如:if/for等)

5. JSX的条件渲染

const isloading = true

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

const title=(
    <h1>
        条件渲染:{loadData()}
    </h1>
)

6. JSX的列表渲染

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

注意:渲染列表时应该添加key属性,key属性的值要保证唯一

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

注意:尽量避免使用索引号作为key

const songs=[

    {id:1,name:’痴心绝对’},
    {id:2,name:’像我这样的人’},
    {id:3,name:’南山南’},
]

const list=(
    <ul>
        {songs.map(item=><li key={item.id}>{item.name})</li>
    </ul>
)

7. JSX的样式处理

1. 行内样式——style

<h1 style={{ color:’red’,backgroundColor:’skyblue’ }}>
    JSX的样式处理
</h1>

2. 类名——className(推荐)

<h1 className=”title”>
    JSX的样式处理
</h1>

8. JSX是React的核心内容

JSX表示在JS代码中写HTML结构,是React声明式的体现

使用JSX配合嵌入的JS表达式,条件渲染,列表渲染,可以描述任意UI结构

推荐使用className的方式给JSX添加样式。

React完全利用JS语言自身的能力来编写UI,而不是造轮子增强HTML功能。

7. React组件基础

使用React就是在使用组件。组件表示页面中的部分功能,组合多个组件实现完整的页面功能。

组件的特点:可复用,独立,可组合。

1. React组件的两种创建方式

1. 使用函数创建组件

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

约定1:函数名称必须以大写字母开头

约定2:函数组件必须有返回值,表示该组件的结构

如果返回值为null,表示不渲染任何内容

function Hello() {
    return (
        <div>这是我的第一个函数组件!</div>
    )
}

// 渲染函数组件:用函数名作为标签名

ReactDOM.render(<Hello/>,root)

2. 使用类创建组件

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

约定1:类名称也必须以大写字母开头

约定2:类组件应该继承ReactCompinent父类,从而可以使用父类中提供的方法或属性。

约定3:类组件必须提供render()方法

约定4:render()方法必须有返回值,表示该组件的结构

class Hello extends React.Component {
    render(){
        return <div>Hello Class Component!</div>
    }
}

ReactDOM.render(<Hello />,root)

2. 抽离为独立JS文件

项目中的组件多了之后,该如何组织这些组件呢?

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

1. 创建Hello.js

2. 在Hello.js中导入React   //import React from ‘react’

3. 创建组件(函数或类)

4. 在Hello.js中导出该组件   //export default Hello

5. 在index.js中导入Hello组件   //import Hello from ‘./Hello’

6. 渲染组件   //ReactDOM.render(<Hello/>,root)

3. React事件处理

1. 事件绑定

React事件绑定语法与DOM事件语法相似

语法:on+事件名称={事件处理程序},

例如:onClick={()=>{}}

注意:React事件采用驼峰命名法:比如:onMouseEnter,onFocus

在类组件中绑定事件

class App extends React.Component{
    handleClick(){
        console.log(‘单击事件触发了’)
    }

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

在函数组件中绑定事件:

function App() {
    function handleClick() {
        console.log(‘单击事件触发了’)
    }
    return(
        <button onClick={handleClick}>点我</button>
    )
}

2. 事件对象

可以通过事件处理程序的参数获得到事件对象。

React中的事件对象叫做:合成事件(对象)

合成事件:兼容所有浏览器,无需担心跨浏览器兼容性问题。

function handleClick(e){
    e.preventDefault()
    console.log(‘事件对象’,e)
}

<a onClick={handleClick>点我,不会跳转页面</a>

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

函数组件又叫做无状态组件,类组件有叫做有状态组件

状态(state)即数据

函数组件没有自己的状态,只负责数据展示(静)

类组件有自己的状态,负责更新UI,让页面“动”起来。

比如计数器案例中,点击按钮让数值加1。0和1就是不同时刻的状态,而由0变为1就表示状态发生了变化。

状态变化后,UI也要相应的更新。React中想要实现该功能,就要使用有状态组件来完成。

5. 组件中的state和setState

状态(state)即数据,是组件内部的私有数据,只能在组件内部使用

1. 通过this.state来获取状态

state的值是对象,表示一个组件中可以有多个数据。

class Hello extends React.Component{
    constructor(){
        super()   //ES6规则
        //初始化state
        this.state={
            count:0
        }
    }
    render(){
        return(
            <div>有状态组件</div>
        )
    }
}
class Hello extends React.Component{
    //简化语法
    state={
        count:0
    }
    render(){
        return(
            <div>有状态组件,{this.state.count}</div>
        )
    }
}

2. setState()修改状态

状态是可变的

语法:this.setState({要修改的数据})

注意:不要直接修改state中的值,这是错误的!!!

setState()作用:1.修改state 2. 更新UI

思想:数据驱动视图

//正确
this.setState({
    count:this.state.count+1
})

//错误
this.state.count +=1

6. 事件绑定this指向

1. 箭头函数

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

class Hello extends React.Component{
    onIncrement(){
        this.setState({...})
    }
    render(){
        //箭头函数中的this指向外部环境,此处为:render()方法
        return(
            <button onclick={()=>this.onIncrement()}></button>
        )
    }
}

2. Function.prototype.bind()

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

class Hello extends React.Component{
    constructor(){
        super()
        this.state={
            count:0
        }
        this.onIncrement=this.onIncrement.bind(this)
    }
    //事件处理程序
    onIncrement(){
        this.setState({
            count:this.state.count+1
        })
    }
    render(){
        //箭头函数中的this指向外部环境,此处为:render()方法
        return(
            <div>
                <h1>计数器:{this.state.count}</h1>
                <button onclick={this.onIncrement}>+1</button>
            </div>    
        )
    }
}

3. class实例方法

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

注意:该语法是实验性语法,但是,由于babel的存在可以直接使用。

class Hello extends React.Component{
    state={
        count:0
    }
    //事件处理程序(把方法自身改造成箭头函数)
    onIncrement=()=>{
        this.setState({
        count:this.state.count+1
        })
    }
    render(){
        //箭头函数中的this指向外部环境,此处为:render()方法
        return(
            <button onclick={this.onIncrement}>+1</button>
        )
    }

}

7. 表单处理

1. 受控组件

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

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

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

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

<input type=”text” value={this.state.txt} />

步骤:

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

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

class App 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>
        )
    }
}

文本框,富文本框,下拉框操作value属性

复选框操作checked属性

多表单元素优化:使用一个事件处理程序同时处理多个表单元素。

1. 给表单元素添加nama属性,名称与state相同

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

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

2. 非受控组件(DOM方式)

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

使用步骤:

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

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

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

class App extends React.Component{
    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. React组件通讯

组件是独立且封闭的单元,默认情况下,只能使用自己的数据。

而多个组件之间不可避免的要共享某些数据。为了实现这一点,就需要打破组件的独立封闭性,让其与外界沟通,这个过程就是组件通讯

1. 通过props实现

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

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

传递数据:给组件标签添加属性。

<Hello name=”jack” age={19} />

接收数据:函数组件通过参数props接收数据,类组件通过this.props接收数据

function Hello(props){
    return(
        <div>接收到数据:{props.name}</div>
    )
}

2. props传递数据的特点

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

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

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

class Hello extends React.Component{
    constructor(props){
        //推荐将props传递给父类构造函数
        super(props)
    }
    render(){
        return <div>接收到的数据:{this.props.age}</div>
    }
}

3. 组件通讯的三种方式

1. 父组件→子组件

1.1 父组件提供要传递的state中的数据

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

1.3 子组件中通过props接收父组件中传递的数据

2. 子组件→父组件

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

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

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

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

3. 兄弟组件

将共享状态提升到最近得公共父组件中,由公共父组件管理这个状态。

公共父组件职责:1. 提供共享状态;2. 提供操作共享状态得方法

要通讯的子组件只需通过props接收状态或操作状态的方法。

4. 跨组件传递数据Context

作用:跨组件传递数据

使用步骤:

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

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

2. 使用Provider组件作为父节点

<Provider>
    <div className=”App”>
        <Childl />
    </div>
</Provider>

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

<Provider value=”pink”>

4. 调用Consumer组件接收数据

<Consumer>
    <data =><span>data参数表示接收到的数据--{data}</span>
</Consumer>

5. props深入

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

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

6. props校验

对于组件来说,props是外来的,无法保证组件使用者传入什么格式的数据。

如果传入的数据格式不对,可能会导致组件内部报错。

1. 使用props校验的原因

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

props校验:允许在创建组件的时候,就指定props的类型,格式等。

作用:捕获使用组件时因为props导致的错误,给出明确的错误提示,增强组件的健壮性。

App.proTypes={
    colors:PropTypes.array
}

2. 使用步骤

1. 安装包prop-types(npm install prop-types)

2. 导入prop-types包(import ProTypes from ‘prop-types’)

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

4. 校验规则通过PropTypes对象来指定(例如PropTypes.array)

3. 约束规则

1. 常见类型:array,bool,func,number,object,string

2. React元素类型:element

3. 必填项:isRequired

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

//常见类型
optionalFunc:PropTypes.func,

//必选
requiredFunc:PropTypes.func.isRequired,

//特定结构的对象
optionalObjectWithShape:PropTypes.shape({
    color:PropTypes.string,
    fontSize:PropTypes.number
})

4. props的默认值

场景:分页组件——》每页显示条数

function App(props){
    return (
        <div>
            此处展示props的默认值:{props.pageSize}
        </div>
    )
}
//设置默认值
App.defaultProps={
    pageSize:10
}

//不传入pageSize属性
<App />

9. 组件的生命周期

组件的生命周期,有助于理解组件的运行方式,完成更复杂的组件功能,分析组件错误原因等。

组件的生命周期:组件从被创建到挂载到页面中运行,再到组件不用时卸载的过程。

生命周期的每个阶段总是伴随着一些方法调用,这些方法就是生命周期的钩子函数

钩子函数的作用:为开发人员在不同阶段操作组件提供了时机。

只有类组件才有生命周期

1. 生命周期的三个阶段

创建时→更新时→卸载时

1. 创建时(挂载阶段)

执行时机:组件创建时(页面加载时)

执行顺序:

constructor()→render()→componentDidMount()

钩子函数

触发时机

作用

constructor

创建组件时,最先执行

初始化state

为事件处理程序绑定this

render

每次组件渲染都会触发

渲染UI

不能调用setState()

componentDidMount

组件挂载(完成DOM渲染)后

发送网络请求

DOM操作

2. 更新时(更新阶段)

执行时机:1. setState() 2. forceUpdate() 3. 组件接收到新的props

说明:以上三者任意一种变化,组件就会重新渲染。

执行顺序:

render()→componentDidUpdate()

钩子函数

触发时机

作用

render

每次组件渲染都会触发

渲染UI

与挂载阶段是同一个render

componentDidUpdate

组件更新(完成DOM渲染)后

发送网络请求

DOM操作

如果要setState()必须放在一个if条件中

3. 卸载时(卸载阶段)

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

钩子函数

触发时机

作用

componentWillUnmount

组件卸载

执行清理工作

比如清理定时器

2. React组件复用

复用的是:1. state 2. 操作state的方法

两种方式:1. render props模式 2. 高阶组件(HOC)

注意:这两种方式不是新的API,而是利用React自身特点的编码技巧,演化而成的固定模式(写法)。

1. render props模式

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

问题1:如何拿到该组件中复用的state?

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

<Mouse render={ (mouse)=>{} } />

问题2:如何渲染任意的UI?

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

<Mouse render={ (mouse)=>(<p>鼠标当前位置{mouse.x},{mouse.y}</p>) }

children代替render属性

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

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

推荐:使用children代替render属性。

<Mouse>
    { ({x,y})=><p>鼠标的位置是{x},{y}</p>}
</Mouse>

//组件内部:

this.props.children(this.state)

代码优化

// 推荐:给render props模式添加props校验
Mouse.propTypes={
    children:PropTypes.func.isRequired
}

// 应该在组件卸载时解除mousemove事件绑定
componentWillUnmount(){
    window.removeEventListener(‘mouse’,this.handleMouseMove)
}

2. 高阶组件

目的:实现状态逻辑复用,采用包装模式。

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

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

const EnhancedComponent=withHOC(WrappedComponent

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

使用步骤:

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

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

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

function withMouse(WrappedComponent){
    class Mouse extends React.Component{}
        return Mouse
}

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

//Mouse组件的render方法中:
return <WrappedComponent {...this.state} />

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

//创建组件
const MousePosition=withMouse(Position)

//渲染组件
<MousePosition />

3. 设置displayName

使用高阶组件存在的问题:得到的两个组件名称相同。

原因:默认情况下,React使用组件名称作为displayName

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

displayName的作用:用于设置调试信息(ReactDeveloper Tools信息)

Mouse.displayName=`WithMouse${getDisplayName(WrappedComponent)}`

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

渲染WrappedComponent时,将state和this.props一起传递给组件

<WrappedComponent {...this.state} {...this.props} />

10. React原理

1. setState()的说明

setState()是异步更新数据的

this.state={count:1}
this.setState({
    count:this.state.count+1
})
//此处打印出来的是1
console.log(this.state.count)   

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

可以多次调用setState(),只会触发一次重新渲染。

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

注意这种语法也是异步更新state的!

参数state:表示最新的state

参数props:表示最新的props

this.setState((state,props)=>{
    return {
        count:state.count+1
    }
})
console.log(this.state.count)

第二个参数:

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

语法:setState(updater,[callback])

this.setState(
    (state,props)=>{},
    ()=>{console.log(‘这个回调函数会在状态更新后立即执行’)}
)

2. JSX语法的转化过程

JSX仅仅是createElement()方法的语法糖(简化语法)

JSX语法:

const element=(
    <h1 className=”greeting”>
        Hello JSX!
    </h1>
)

createElement()语法

const element=React.createElement(‘h1’,{className:’greeting’},‘Hello JSX!’);

JSX语法被@babel/preset-react插件编译为createElement()方法

React.createElement又在内部被转化为了React元素

React元素:是一个对象,用来描述你希望在屏幕上看到的内容。

3. 组件更新机制

setState()的两个作用:1. 修改state 2. 更新组件(UI)

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

4. 组件性能优化

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

注意:不用做渲染的数据不要放在state中,比如定时器id等

对于这种需要在多个方法中用到的数据,应该放在this中

class Hello extends Component{
    componentDidMount(){
        //timerId存储到this中,而不是state中
        this.timerId=setInterval(()=>{},2000)
    }
    componentWillUnmount(){
        clearInterval(this.timerId)
    }
    render(){...}
}

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

组件更新机制:父组件更新会引起子组件也被更新。

问题:子组件没有任何变化时也会重新渲染。

解决办法:用钩子函数shouldComponentUpdate(nextProps,nextState)

作用:通过返回值决定该组件是否重新渲染,返回true表示重新渲染,返回false表示不重新渲染。

触发时机:更新阶段的钩子函数,组件重新渲染前执行

(shouldComponentUpdate→render)

shouldComponentUpdate(nextProps,nextState){
    //最新的state,当前的state
    if(nextState.number === this.state.number){
        return false
    }
    return true
}

3. 纯组件

纯组件:PureComponent与React.Component功能相似

class Hello extends React.PureComponent{
    render(){
        return(
            <div>纯组件</div>
        )
    }
}

区别:PureComponent内部自动实现了shouldComponentUpdate钩子,不需要手动比较。

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

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

对于值类型来说:比较两个值是否相同(直接赋值即可,没有坑)

state={ number:0 }
setState({
    number:Math.floor(Math.random()*3)
})

//PureComponent内部对比:

// 最新的state.number === 上一次的state.number  //false,重新渲染

对于引用类型来说:只比较对象的引用(地址)是否相同

const obj={number:0}
const newObj=obj
newObj.number=2
console.log(newObj === obj)  //true

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

//正确!创建新数据

const newObj={ ...state.obj,number:2}
setState({obj:newObj})

//正确!创建新数据

//不要用数组的push/unshift等直接修改当前数组的方法

//而应该用concat或slice等这些返回新数组的方法

this.setState({
    list:[...this.state.list,{新数据}]
})

5. 虚拟DOM和Diff算法

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

特点:思路非常清晰。

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

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

React就是通过虚拟DOM配合Diff算法做到部分更新的。

1. 什么是虚拟DOM

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

虚拟DOM对象

const element={
    type:’h1’,
    props:{
        className:’greeting’,
        children:’Hello JSX!’
    }
}

HTML结构

<h1 class=”greeting”>
    Hello JSX!
</h1>

2. 执行过程

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

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

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

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

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

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

render方法调用并不意味着浏览器中的重新渲染!!!

render方法调用仅仅说明要进行diff。

虚拟DOM的真正价值从来都不是性能,它让React脱离了浏览器环境的束缚。

11. React路由基础

现代的前端应用大多都是SPA(单页面应用程序),也就是只有一个HTML页面的应用程序。因为它的用户体验更好,对服务器的压力更小,所以更受欢迎。

为了有效的使用单个页面来管理原来多页面的功能,前端路由应运而生。

前端路由的功能:让用户从一个视图(页面)导航到另一个视图(页面)

前端路由是一套映射规则,在React中,是URL路径与组件的对应关系。

使用React路由简单来说,就是配置路径和组件(配对)。

1. 使用步骤

1. 安装npm instasll react-router-dom

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

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

3. 使用Router组件包裹整个应用(重要)

<Router>
    <div className=”App”>
        //...省略页面内容
    </div>
</Router>

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

<Link to=”/first”>页面一</Link>

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

2. 两种常用Router

HashRouter和BrowserRouter

HashRouter:使用URL的哈希值实现(localhost:3000/#/first)

BrowserRouter:使用H5的history API实现(localhost:3000/first)

Link组件:用于指定导航链接(最终都会被编译成a标签)

to属性:浏览器地址栏中的pathname(location.pathname)

Route组件:指定路由展示组件相关信息

//path属性:路由规则

//component属性:展示的组件

//Route组件写在哪里,渲染出来的组件就在哪里

<Route path=”/first” component={First}></Route>

3. 路由的执行过程

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

2. React路由监听到地址栏URL的变化。

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

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

4. 编程式导航

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

history是React路由提供的,参数path表示要跳转的路径

push(path):跳转到某个页面,参数path表示要跳转的路径

go(n):前进或后退

class Login extends Component {
    handleLogin=()=>{
        //...
        this.props.history.push(‘/home’)
    }
    render(){...省略其他代码}
}

5. 默认路由

表示进入页面时就会匹配的路由,path为:/

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

6. 匹配模式

1. 模糊匹配模式

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

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

// 如上述,/login以/开头。

// pathname代表Link组件的to属性
<Link to=”/login”>登录页面</Link>

// path代表Route组件的path属性
<Route path=”/” component={Home} />匹配成功

2. 精确匹配模式

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

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

//此时,该组件只能匹配pathname=”/”这一种情况
<Route exact path=”/” component=.../>

 

;