React基础学习

一、学习主要内容

  • 浏览器原理
  • 建设高性能网站
  • 组件开发
  • 高级语法
  • SSR
  • Redux
  • React Router
  • React UI
  • create-react-app
  • 单元测试
  • ReactNative
  • 项目实战

1.1 获取react

Git地址:

  • https://github.com/facebook/react

获取React:

  • npm install react react-dom

注: 在ES5开发中,可以通过bower获取react.js和react-dom.js文件,我们使用最新版本中,只能使用ES6开发

二、 组件开发

2.1 创建虚拟DOM

核心库: react提供了createElement方法用来创建虚拟DOM

  • 第一个参数表示虚拟DOM名称(也可以是组件名称)
  • 第二个参数表示虚拟DOM的属性对象
  • 从第三个参数开始,表示子虚拟DOM

2.2 虚拟DOM简介

虚拟DOM是一个普通js对象,存储一些必要属性来描述DOM

  • type 表示虚拟DOM类型
  • key 表示虚拟DOM的id
  • ref 表示对真实DOM的引用
  • props 表示虚拟DOM中所有属性对象
  • children 表示虚拟DOM的子虚拟DOM
  • 字符串 表示一个文本节点
  • 对象 表示一个虚拟DOM
  • 数组 表示多个虚拟DOM或者文本。

2.3 渲染虚拟DOM

浏览器端渲染库react-dom,提供了render方法,可以渲染虚拟DOM

  • 第一个参数表示虚拟DOM
  • 第二个参数表示真实DOM容器元素
  • 第三个参数是回调函数,表示渲染完成执行的方法

2.4 组件

虚拟DOM与页面中的真实DOM相对应,因此一个虚拟DOM对应一个真实DOM

如果页面中有很多真实DOM,我们就要创建很多虚拟DOM

为了复用虚拟DOM,react提供了组件技术

定义组件就是一个类:

  • 在ES5开发中,通过React.createClass方法创建组件类

  • 在ES6开发中,通过class定义组件类

    语法: class 组件类名{}

注意: 类名首字母大写。

2.5 JSX语法

我们创建虚拟DOM太麻烦了,不如在html页面中创建元素方便。

  1. 在xhtml中创建div:
  2. 在jsx语法中创建div:

JSX语法已经被纳入ES2017规范了

目前浏览器不支持JSX语法,因此我们要编译JSX语法。

编译jsx语法:

我们编译jsx语法使用: React编译器

我们既想编译ES6语法,也想编译jsx语法,就要同时使用es2015与react编译器。在webpack4.0中,我们要使用最新的编译器:

[’ @babel/preset-env ', ‘@babel/preset-react’]

扩展名:

  • 为了区分ES5语法和ES6语法,我们将文件拓展名定义成.es
  • 为了区分ES5语法和jsx语法,我们可以将文件的拓展名定义成.esx,.jsx,.tsx,.es6x…
  • 为了语义化,react团队建议我们定义成.jsx

2.6 插值语法

插值语法: {}

  • 是一个真正的js环境,可以书写复杂的表达式
  • 由于插值语法提供的是真正的js环境。因此可以继续使用jsx语法

注: js语法与jsx语法可以嵌套使用

2.7 属性数据

// 需要安装prop-types: npm install prop-types
// 约束属性数据
Demo.proTypes = {
    // 设置isRequire不能设置默认属性数据
    data: ProTypes.array.isRequire
}
// 默认数据
Demo.defaultProps = {
    data: ['默认数据']
}

虚拟DOM有四类属性: 自定义属性,元素属性,特殊元素属性,非元素属性。

  1. 自定义属性:是用来存储数据的。没有特殊含义
    • html5中,自定义属性建议以data-开头
  2. 元素属性:对于元素来说有特殊的含义。列如:id,title,style等
    • 注: style的属性值只能是对象。如:style={{ key: value }}外面的{}表示插值符号,内部的{}表示对象符号,样式名称要使用驼峰式命名
    • 注: 如果样式属性值表示长度单位,并且单位是像素,则像素额可以省略
  3. 特殊元素属性:虚拟DOM有两个常见的特殊元素属性class和for。它们命中了关键字以及保留字。所以:class要写成className。for要写成htmlFor

非元素属性 – ref

非元素属性:

是由React拓展的,不是DOM元素默认的属性,如:ref,dangerouslySetInnerHTML,key,非元素属性是react提供的功能,类似vue中的指令

ref

允许我们在组件中获取虚拟DOM对应的真实DOM元素(也可以是自定义组件)

​ 列如:在componentDidMount(组件创建完成)方法中获取

​ 分成两步:

					1. 为元素设置ref属性
					2. 在组件中,通过this.refs获取对应的元素

注意:如果获取的是元素,则是原生的DOM元素,因此要通过原生的DOM API操作。

​ 通常在一些第三方插件中使用

​ 我们直接操作真实DOM元素就跳过虚拟DOM环节,因此这些操作会被React忽略。无法被虚拟DOM优化,工作中慎用

非元素属性 – dangerouslySetInnerHTML

用危险的(不合法的)方式设置元素的内容。

​ 不合法的方式有:

	1. 不能使用行内式样式字符串
	2. 不能直接使用特殊元素属性
	3. 不能渲染标签等等...

我们可以借组dangerouslySetInnerHTML属性实现这些功能。属性值是对象,通过__html设置内容。

注意:

  1. 该属性也是直接对真实DOM元素设置innerHTML属性,因此跳过了虚拟DOM优化环节,工作中慎用

    1. 设置的内容要保证是安全的。否则可能会被攻击

非元素属性–key

为列表的虚拟DOM设置id

虚拟DOM可以包含一个子虚拟DOM或者文本,也可以包含多个子虚拟DOM(是数组)

​ 当包含多个子虚拟DOM的时候,就是一个数组

​ 此时要为每一个成员设置key属性。

注意: 每一个数组中,key属性值要唯一并且稳定、可预测

代码使用

import React, { Component } from 'react'
import { render } from 'react-dom'

// 数据
let msg = '<span>span</span>'
let data = ['如果当时', '忙着微笑', '和哭泣啊']
class Demo extends Component {
    componentDidMount() {
        console.log(this, 'this')
        this.refs.ickt.style = "color: red"
    }
    //定义创建列表的方法
    createList() {
        // return data.map((item, index)=> {
        //     return <li key={index}>{item}</li>
        // })
        // 省略
        return data.map((item, index) => <li key={index}>{item}</li>)
    }
    render() {
        console.log(this.createList(), 'this')
        return (
            <div>
                {/* {自定义数据属性} */}
                <h1 data-ickt="hello">标题</h1>

                {/* {元素属性} */}
                <h1 title="hello" style={{ color: 'red', backgroundColor: 'green' }}>啦啦啦啦</h1>

                {/* {特殊元素属性} */}
                <h1 className="lalala">特殊元素属性</h1>

                <label htmlFor="usernmae">用户名</label>
                <input type="text" id="usernmae" />
                {/* {点击用户名,input获取焦点} */}


                {/* {非元素属性} */}
                <h1 data-num="100" ref="ickt">我是非元素属性</h1>
                {/* dangerouslySetInnerHTML */}
                <h1 dangerouslySetInnerHTML={{ __html: msg }}>
                </h1>

                {/* 通过方法创建数组 */}
                {this.createList()}
            </div>
        )
    }
}
render(<Demo></Demo>, app)

2.8 组件属性

为了增强组件的适用性,我们要为组件定义属性

  1. 属性分类:
    • 虚拟DOM有四类属性:自定义数据属性 ,元素属性,特殊元素属性,非元素属性。
    • 组件只有一类属性数据,就是自定义数据属性
  2. 传递属性:组件是对虚拟DOM的封装,因此为组件传递属性数据跟虚拟DOM传递属性数据的语法是一样的。
    • 虚拟DOM传递属性数据
    • 组件传递属性数据
  3. 使用属性: 在组件中,通过this.props 获取属性数据。

注意:属性数据是组件外部维护的数据,只能在组件外部修改,不能在组件内部修改

import React, { Component } from 'react'
import { render } from 'react-dom'
// 组件
class Demo extends Component {
    
// 静态数据,第二种方式,通过static定义
    get num() {
        return 100
    }
    static getNum() {
        return this.num
    }
    createList() {
        // 使用组件属性数据
        return this.props.data.map(item => <li key={item}>{item}</li>)
    }
    render() {
        return (
            <ul>
                {this.createList()}
            </ul>
        )
    }
}

// 静态数据,第一种方式,外部打点添加, react推荐使用
Demo.color = 'red';
Demo.getColor = function () {
    return 'blue'
}

// 默认属性数据
Demo.defaultProps = {
    data: ['默认数据']
}
console.log(Demo.num, 's')

let data = ['1', '2', '3', '4']

render(<Demo data={data}></Demo>, app)

render(<Demo data={['lalal']}></Demo>, app2)

2.9 DOM事件

react中,为虚拟DOM绑定事件与真实的DOM绑定事件的语法是类似的

真实DOM绑定事件

虚拟DOM绑定事件

注意:

  1. 事件名称首字母要大写
  2. 事件回调函数要定义在组件中
  3. 事件回调函数不要执行(后面不要添加参数集合{})
  4. 默认参数就是事件对象
  5. this指向undefined

三、组件生命周期

为了描述组件创建,存在、销毁的过程,react也为组件提供了生命周期技术

  1. 创建期

  2. 存在期

  3. 销毁期

  • 创建期共分为五个阶段,描述组件创建的过程。

  • 在ES5开发中: getDefaultProps, getInitialState, componentWillMount, render, componentDidMount

  • 在ES6开发中,创建期的前两个阶段方法有所改变

3.1 创建期

  1. 定义组件默认属性数据: defaultProps静态属性。此时组件尚未创建。属性值就是默认的属性数据。没有为组件传递属性数据,组件救护使用该默认数据

  2. 构造函数(初始化状态数据): constructor。构造函数有个参数: 属性数据,上下文数据。

    构造函数中,要使用super方法实现构造函数式继承。我们要向super方法传递属性数据和上下文数据。如果没有传递属性数据和上下文数据: 此时参数props与this.props不用,并且参数context与this.context不同。如果传递了属性数据和上下文数据:此时参数props与this.props全等,并且参数context与this.context全等.

    所以工作中建议传递属性数据和上下文数据,但是由于工作中,上下文数据不会经常使用。所以通常我们只传递属性属性就可以了。在构造函数中,我们为this.state赋值,实现状态数据的初始化

    我们可以用属性数据和上下文数据为状态数据赋值,实现数据由外部流入内部

    这么做的原因是: 可以在组件内部维护(修改)属性数据和上下文数据(不会影响其他组件)

  3. 组件即将构建: componentWillMount。此时已经有了属性数据,状态数据和上下文数据,但是还不能获得虚拟DOM。工作中,我们可以在该阶段发送请求,初始化插件等

  4. 渲染输出虚拟DOM: render。这个方法主要用于渲染虚拟DOM。返回值就是渲染的虚拟DOM,有且只有一个根元素。注意:render。这个方法主要负责渲染,所以不要在该方法中实现业务逻辑(如发送请求等)

    • 不要在该方法中,尝试修改数据,(如状态数据)。虚拟DOM是在返回值中创建的,因此在render方法中,无法获取虚拟DOM对应的真实DOM
  5. 组件构建完毕: compomentDidMount. 此时组件就创建出来了,已经有了属性数据,状态数据,上下文数据以及虚拟DOM。该方法执行完毕,标志着创建期的结束,存在期的开始。我们可以在该方法中,绑定事件(全局),发送请求,使用插件等等。

注: 创建期的方法在组件的一生中,只能执行一次。后面四个方法中this都指向组件,后三个方法没有参数。

3.2 获取DOM元素

渲染库react-dom提供了findDOMNode方法,可以获取虚拟DOM对应的真实DOM元素

获取的是原生DOM,因此要用源生DOM API去操作

注意: 工作中尽量不要这样去操作虚拟DOM,因为对原生DOM的操作是会被react无视掉

3.3 存在期

存在期跟创建期一样,也分为五个阶段,来描述组件数据更新的过程

一旦组建的属性数据,状态数据或者上下文数据改变,组件就会进入存在期。五个阶段:

  1. 组件即将接收新的属性数据: componentWillReceiveProps。只有属性数据改变,才会执行该阶段方法,状态数据改变不会执行。第一个参数表示新的属性数据。this上的还是旧的属性数据和旧的状态数据。我们可以用属性数据更新状态,实现外部的数据存储到内部。讲外部数据存储到组件内部是该阶段的唯一作用。此时新的属性数据和新的状态数据一同进入第二个阶段方法

  2. 组件是否应该更新:shouldComponentUpdate。第一个参数表示新的属性数据。第二个参数表示新的状态数据。this上的还是旧的的属性数据和旧的状态数据。必须有返回值,表示是否更新:true,表示更新;false,表示不更新,工作中,通常比较属性数据是否改变或者状态数据是否改变来决定是否更新

    作用:起到更新优化的作用(如:在高频事件中应用)

  3. 组件即将更新:componentWillUpdate。第一个参数表示新的属性数据。第二个参数表示新的状态数据。this上的还是旧的属性数据和旧的状态数据。数据在该阶段还没有更新,该阶段执行完毕,数据才会更新,我们可以在该阶段调整插件等等。

  4. 组件渲染虚拟DOM:render。没有参数,数据已经更新了,与创建期render是同一个方法。this上的已经是新的属性数据和新的状态数据了。所以渲染虚拟DOM的时候,就可以使用这些新的数据了

  5. 组件更新完毕: componentDidUpdate。第一个参数表示旧的属性数据。第二个参数表示旧的状态数据。this上的已经是新的属性数据和新的状态数据了。虚拟DOM也可以更新了,所以在该阶段新数据,旧的数据都可以访问到,我们可以在该阶段,继续请求数据,处理插件,调整事件等等。组件已经更新完毕,只是一次更新结束,存在期仍然存在

    注: 这些方法中的this都指向组件

四、高级语法

4.1 PureComponent

该组件主要是为了对类组件做性能优化的

  • 功能类似shouldComponentUpdate方法

使用方式与Component一样:

  • class组件类extends PureComponent{}
    1. 此时当组件的数据改变的时候,组件才会更新
  1. 只对类组件生效,对函数组件无效
  2. 只对数据(属性,状态)做浅层的比较,如果是引用类型的数据,不会深比较。此时还得用Component的shouldComponentUpdate方法

4.2 memo

React提供了memo方法,可以对函数组件做优化

memo(Comp, (currentProps, nextProps)=> {
    // currentProps: 表示当前的属性数据
    // nextProps: 表示新的属性数据
    // 必须有返回值,表示是否不更新:true表示不更新、false表示更新与shouldComponentUpdate返回值正好相反,true表示更新
    
    注意:memo方法只能处理函数组件
    	 memo方法返回值是一个新组件,原来的组件不受影响,返回的新组件才能被优化
         用一个函数处理组件,并返回一个新组件,这个新组件就是高阶组件,这个方法(函数)叫高阶方法
})

4.3 cloneElement

有时候想复制一个虚拟DOM,可以使用cloneElement方法

用法与createElement方式类似

cloneElement(vdom, props, ...children)
	第一个参数表示虚拟DOM
    第二个参数表示属性对象
    从第三个参数开始表示传递给新的虚拟DOM的子内容
    cloneElement与createElement方法一样,也有jsx语法的等价语法。
    <vdom.type key={value}{...props}></vdom.type >
定义虚拟DOM
import React, { Component, cloneElement } from 'react'
let title = <h1 className="demo"> hello </h1>
let obj = {
    id: 'test',
    style: {
        color: 'red',
        fontSize: 40
    }
}
class App extends Component {
    render() {
        return (
        	<div>
            	// 使用虚拟DOM
            	{ title }
            	// 复制虚拟DOM
            	{ cloneElement(title, {title: '专业前端培训学校'}) }
        		// jsx等价语法
        		<title.type title="专业前端培训学校" {...obj}>爱创课堂</title>
            </div>
        )
    }
}

4.4 Fragment

作用类似Vue中的template元素

是用来定义一个模板的组件

  • 该组件可以作为根节点,但是不会渲染到页面中

工作中:

1. 用来渲染多个兄弟根元素			2. 避免在特定元素之间,引入新元素。
render() {    return (    	<Fragment key={item}>        	<li>{item}</li>        </Fragment>    )}

4.5 错误边界组件

在一个组件中,如果某个子组件出现了错误,整个页面都无法渲染了

  • 子组件中的错误冒泡到父组件中,影响了父组件的渲染。

所以react提供了错误边界技术,让错误冒泡到错误边界组件中,这样就不会影响父组件的渲染了

错误边界组件使用:

只需要定义componentDidCatch方法,就可以捕获错误,避免错误冒泡	第一个参数错误信息,    第二个参数表示错误描述信息    在错误边界组件中,可以定义静态方法getDerivedStateFromError。    该方法的返回值表示错误产生的时候,修改的状态数据    
class Error extends Component {    constructor(props)    super(props)    this.state = {        hasError: fasle;    }	// 在这更新状态数据,不会执行存在期,优化性能	static getDerivedStateFromError() {        return (        	hasError: true        )    }	// 获取错误,不要继续冒泡	componentDidCatch(...args) {        console.log(args)    }	render() {        if (this.state.hasError) {            return <h1>has error</h1>        }    }}

4.6 异步组件

Children

不论是虚拟DOM还是组件,其Props属性,都有一个children属性,代表子内容

​ 可以是文本,对象,数组

我们可以直接通过children属性获取内容

异步组件

​ 将所有的组件都打包在一起,会导致文件很大,加载很慢,影响用户体验

​ 所以我们要使用异步组件,让我们可以异步的加载组件

​ 我们要使用import方法来异步加载,需要安装插件:

​ babel-plugin-syntax-dynamic-import

注: webpack 4.0 的babel插件已经支持异步加载了

4.7 组件的约束性

用户可以与组件中的表单元素交互,这样就产生了数据,这些数据存储在哪里决定了组件的约束性

如果交互产生的数据存储在元素自身,该组件就是非约束性组件

如果交互产生的数据存储在组件状态中,该组件就是约束性组件

非约束性组件

交互产生的数据存储在元素自身,该组件就是一个非约束性组件。获取数据,存储数据都要通过该元素。

默认值:

  1. 我们通过defaultValue或者defaultChecked属性定义默认值。都是非元素属性。

获取数据:

  1. 为元素设置ref属性

    1. 通过this.refs获取元素,再通过元素获取数据

修改数据:

  1. 为元素设置ref属性

    1. 通过this.refs获取元素,再修改元素的数据
render() {    return (    	<input type="text" defaultValue="hello" defaultChecked ref="username"></input>    )}

Ref

react提供了一种新的创建ref对象的方式

​ 通过createRef可以创建一个ref对象

目的:代替Ref字符串方式

优势:允许我们在组件外部获取组件内部的元素

使用ref对象分成三步

  1. 在构造函数中,创建ref对象,并存储在组件自身
  2. 让ref对象指向元素
  3. 通过ref对象的current属性获取元素
import React, {component, createRef} from 'react'constructor(props) {    super(props)    // 创建ref对象    this.user = createRef()}// 获取数据getData() {    console.log(this.user.value)}// 修改数据changeDate() {    this.user.current.value = "xiugai"}render() {    return (    	<input ref={this.user} defaultValue="hello"></input>    )}

约束性组件

交互产生的数据存储在组件状态中,该组件就是一个约束性组件。获取数据,修改数据都通过组建的状态

默认值 :

  1. 为value属性和checked是属性绑定组件的状态数据
  2. 在onChange方法中,监听数据的改变,并更新状态
  3. 如果是输入框,我们还可以做表单校验。判断内容是否合法:合法则更新状态、不合法则不更新状态

获取数据“:

  • 就是获取组件的状态数据

修改数据

  • 就是修改组件的状态数据

4.8 高阶组件

在react组件中,为了保证组件基类的完整性,我们对组件扩展功能的时候,react不允许我们修改组件的基类

如果对组件扩展功能,React提供了高阶组件的技术:类似装饰者模式,不用直接修改原来的组件,而是定义一个新组件,对原来组件做包装

​ 使用高阶组件技术,需要定义一个高阶函数(方法)

​ 参数是原组件,以及给高阶组件传递的数据。在函数中,我们创建一个新组件,

​ 在render方法中,我们渲染原来的组件并传递相应的属性数据

​ 我们对新组件进行包装(扩展),以此来实现对原来组件的扩展(没有被修改)。

​ 返回新组件,使用的时候,使用的是新组件

import React, { Component } from 'react'import { render } from 'react-dom'class App extends Component {    constructor(props) {        super(props)        this.state = {            msg: 'hello'        }    }    render() {        let { msg } = this.state        return (            <div>                <input type="text" value={msg} onChange={e => this.setState({ msg: e.target.value })} />                <hr />                <Demo msg={msg}></Demo>                <hr />                <IcktDemo msg={msg}></IcktDemo>            </div>        )    }}class Demo extends Component {    // 组件即将接受新的属性数据    render() {        return (            <div>                <h1>{this.props.msg}</h1>            </div>        )    }}// 定义高阶方法function ickt(comp) {    // 定义组件    class Ickt extends Component {        // 通过对新组件的扩展,实现需求,不影响原来的组件        // 渲染方法中,输出原来的组件        render() {            // 将ickt接受的属性,传递给demo组件            console.log(this.props)            return <Demo {...this.props}></Demo>        }    }    return Ickt}// 通过高阶方法,创建高阶组件let IcktDemo = ickt(Demo)render(<App></App>, app)

代码实现memo

import React, { Component, PureComponent, memo } from 'react'import { render } from 'react-dom'class App extends Component {    constructor() {        super()        this.state = {            isShow: 'none'        }    }    componentDidMount() {        window.addEventListener('scroll', () => {            if (scrollY > 200) {                this.setState({                    isShow: 'block'                })            } else {                this.setState({                    isShow: 'none'                })            }        })    }    render() {        return <NewDemo {...this.state}></NewDemo>    }}function icktMemo(Comp, fn) {    class Ickt extends Component {        shouldComponentUpdate(newProps) {            return !fn(this.props, newProps)        }        render () {            return <Comp {...this.props}></Comp>        }    }    return Ickt}let Demo = props=> {    console.log(props, 'props')    return (        <div style={{            position: 'fixed',            right: 100,            bottom: 50,            background: 'red',            width: 40,            height: 40,            color: 'black',            padding: 20,            cursor: 'pointer',            textAlign: 'center',            lineHeight: '20px',            display: props.isShow        }}>返回顶部</div>    )}let NewDemo = icktMemo(Demo, (currentProps, nextProps)=> {    return currentProps.isShow === nextProps.isShow})render(<App></App>, app)

4.9 ref转发

ref转发就是让我们组件的外部访问组件内部的元素。

我们使用ref有两种方式

  1. 使用ref字符串
    • 在组件内部通过this.refs获取对应的元素(组件内部获取)
  2. 通过createRef创建ref对象
    • 通过ref对象的current属性获取对应的元素(允许在组件外部获取)

注: ref 属性既可以添加给虚拟DOM,也可以添加给组件。

import React, { Component, createRef, forwardRef } from 'react'import { render } from 'react-dom'let newRef = createRef()class App extends Component {    render() {        return (            <div>                {/* {不建议在组件内部访问组件外部的变量} */}                {/* {指向了组件就不能再指向虚拟DOM了} */}                <h1 ref={this.props.refData}>1-1-1</h1>                {/* {一个ref对象只能指向一个虚拟DOM} */}                <h1 ref={this.props.refData}>1-1-2</h1>                <h1>3-3-3</h1>            </div>        )    }}// 函数组件没有状态属性和生命周期// let App = props=> {//     console.log(props, 'props')//     return (//         <div>//             <h1 ref={props.refData}>'巨人'+1-1-1</h1>//             <h1>'巨人'+2-2-2</h1>//             <h1>'巨人'+3-3-3</h1>//         </div>//     )// }// 只是传递,不要指向组件// 函数组件不能直接指向组件,会报错 (使用forwardRef())// let DealApp = forwardRef((props, ref)=> {//     // 返回值中,返回函数组件 取消了ref指向//     console.log(props, ref, 'DealApp')//     return <App {...props} refData={ref}></App>// })// render(<DealApp ref={newRef} color="red"></DealApp>, app, ()=> {//     console.log(newRef)// })// 取消类组件的ref指向let DealApp = forwardRef((props, ref)=> {    console.log(props,ref,  'props')    return <App {...props} refData={ref}></App>})render(<DealApp ref={newRef} color='red'></DealApp>, app, ()=> {    console.log(newRef)})

ref转发在高阶组件中的使用

import React, { Component, forwardRef, createRef } from 'react'import { render } from 'react-dom'class App extends Component {    constructor() {        super()        this.state = {            msg: 'msg'        }        this.ickt = createRef()    }    render() {        return (            <div>                <h1>App</h1>                <Demo {...this.state}></Demo>                <IcktDemo {...this.state} ref={this.ickt}></IcktDemo>            </div>        )    }}class Demo extends Component {    render() {        console.log(this.props, 'Demo')        return (            <div>                <h1 ref={this.props.icktRef}>{this.props.msg}</h1>            </div>        )    }}function ickt(Cmp) {    class Ickt extends Component {        render() {            return (                <Cmp {...this.props}></Cmp>            )        }    }    return forwardRef((props, ref) => {        return <Ickt {...props} icktRef={ref}></Ickt>    })}let IcktDemo = ickt(Demo)render(<App></App>, app)

5.0 Hook

hook是react新版本中提供一项技术,用来为函数组件扩展功能的

函数组件是一个十分简单的组件,是对类组件的简化,因此没有继承组件基类,不具有组件的行为。例如:状态数据,生命周期等等。所以为了让函数具有这些行为,我们可以用hook方法对函数组件做扩展。

使用状态数据

语法: let [数据, 修改数据的方法] = useState(默认数据值);   修改数据的方法: 参数就是新的状态书   在函数组件中,用’修改数据的方法‘去修改数据,此时函数组件将进入存在期,更新虚拟DOM

使用生命周期的方法

语法:use Effect(fn)fn代表componentDidMount以及componentDidUpdate方法...hook为函数组件扩展了大量的功能useEffect(() => {  document.title = `You clicked ${count} times`;}, [count]); // 仅在 count 更改时更新

五、 SSR

5.1 服务器端渲染

前端渲染的问题

  1. 白屏时间长,影响用户体验
  2. 不利于SEO优化

所以我们要使用服务器端渲染,来解决这些问题

React实现了多端适配,为不同的端提供了不同的渲染库,React核心库只负责创建虚拟DOM和组件

​ 在web端渲染,使用react-dom渲染库

​ 在服务器渲染,使用react-dom/server.js渲染库

服务器端渲染库

react-dom/server.js提供了在服务器端渲染的方法

​ renderToString 将应用程序渲染成模板字符串

​ renderTostaticMarkup 将应用程序渲染成模板字符串,并且会删除不必要的属性

​ 例如: data-reactroot等属性

​ 该方法减少了文件大小,因此常常用在渲染静态页面中

服务器端渲染的问题是:无法绑定交互

六、 Redux

redux是一个通用的框架,因此在不同的框架中使用,要安装相应的插件

  • 在react中使用,要安装react-redux
  • vuex之所以能够解决组件之间的通信,是因为vuex为每一个组件都扩展了
  • 发布信息的方法:commit和dispatch
  • 获取store的数据:state和getters

因此想在react中,使用redux解决通信,就要让每一个组件获取store中的state数据,以及发布信息的方法(dispatch)

react-redux提供了connect方法以及Provider组件,可以解决上述问题

redux是通过属性传递数据实现的。

6.1 connect

该方法是用来为组件提供了state数据以及dispatch方法的

两个参数都是函数

  • 第一个参数函数表示: 如果为组件的属性扩展state数据
    • 参数是state数据,返回值是为组件的属性扩展的state数据(还可以扩展更多的数据)
  • 第二个参数函数表示: 如何为组件的属性扩展dispatch方法
    • 参数表示dispatch方法,返回值是为组件的属性扩展的dispatch方法(还可以扩展更多的方法)connect方法返回一个函数(高阶函数)

该函数创建的组件(高阶组件)可以接受state数据集和dispatch方法

没有通过该函数处理的组件(包括原组件)是不会接受state数据和dispatch方法的

6.2 Provider

该组件是为组件提供store数据源的

  • 通过store属性提供数据源。属性值就是store对象
  • 我们可以将组件放在该组件内部,就可以接受数据了

在react中使用redux共分两步

  1. 通过connect方法扩展组件,接受state数据和dispatch方法
  2. 通过Provider组件提供store数据源

让其他的组件接受state数据和dispatch方法有两种方式:

  1. 可以通过父子组件通信的方法,传递数据和方法
  2. 继续使用connect方法得到的高阶函数扩展组件

代码使用

import React, { Component } from 'react'import { render } from 'react-dom'import { createStore } from 'redux'import { connect, Provider } from 'react-redux'// 类型给const ADD = 'ADD'const DEL = 'DEL'let addNum = { type: ADD, data: 5 }let delNum = { type: DEL, data: 5 }function reducer(state = 0, action) {    // 根据action维护state数据    switch (action.type) {        case ADD:            state += action.data            break;        case DEL:            state -= action.data        default:            break;    }    return state}let store = createStore(reducer)class App extends Component {    constructor(props) {        super(props)    }    render() {        const { state, dispatch, addNum } = this.props        return (            <div>                <h1>{'state:' + this.props.state}</h1>                <Slider state={state} dispatch={dispatch} addNum={addNum}></Slider>                {/* <Demo></Demo> */}                {/* 使用扩展后的高阶组件,才能获取数据 */}                <DealDemo></DealDemo>            </div>        )    }}class Slider extends Component {    render() {        // console.log(this, 'Slider')        const { state, dispatch, addNum } = this.props        return (            <div>                <h2>                    <button onClick={e => addNum(5)}>增加5</button>                    <button onClick={e => addNum(10)}>增加10</button>                    {'numSlider :' + state}                </h2>            </div>        )    }}class Demo extends Component {    render() {        const { state, dispatch } = this.props;        return (            <div>                <h2>                    <button onClick={e => dispatch(delNum)}>减少5</button>                    {'numDemo :' + state}                </h2>            </div>        )    }}let deapFn = connect(    state => ({        state    }),    dispatch => ({        dispatch,        addNum(data) {            dispatch({ type: 'ADD', data })        }    }))let DeapApp = deapFn(App)let DealDemo = deapFn(Demo)render(    <Provider store={store}>        <DeapApp></DeapApp>    </Provider>    , app)

6.3 action

静态action

  • 静态action就是一个对象:type定义类型,其他属性定义数据
  • 我们目前定义的action都是静态action
  • 由于静态action中数据是不变的,因此使用的时候不够灵活。适用性不强

动态action

  • 动态action是一个方法,使用的时候要执行,并传递数据
  • 在方法中,返回action对象,action对象中,存储参数数据
  • 这样action中的数据是灵活可变的,适用性强,工作中常用。动态action与拓展属性方法的区别是:
    1. 拓展属性方法可以灵活传递数据,但是会污染组件的属性对象
    2. 动态action可以灵活传递数据,并且不会污染组件的属性对象

6.4 异步action

在一个组件中发送请求,获取的数据要在其他的组件中使用。此时可以使用异步action

异步action跟动态action类似

  • 是一个方法,参数是传递的数据
  • 返回值是一个方法
    1. 第一个参数是dispatch(常用)
    2. 第二个参数是getState
    3. 在方法中,做异步事情。异步结束之后,再发布同步信息

中间件

reedux默认不支持异步action,想使用异步action要为redux安装中间件插件

redux模块提供了applyMiddleware方法

  • 参数是中间件
  • 返回时是一个方法,用来拓展createStore方法
  • 返回值是一个新的createStore方法
  • 用该方法创建的store就支持异步action了

异步action中间件插件: redux-thunk

七、Router

路由

react有三个特点:虚拟DOM,组件化开发,多端适配

所以react路由为了适配多端,为不同的端提供了不同的路由模块

  • 在web端,使用react-router-dom模块
  • 在native端,使用react-router-native模块

它们都依赖react-router模块

所以要安装两个模块 : react-router, react-router-dom

注意: 不同的react版本使用路由的方式不同

使用路由

  • 第一步通过Switch组件定义路由渲染位置,通过Route组件定义路由规则

    path 定义规则(与vue规则一样)	name 定义名称component 定义组件			exact 是否精准匹配注意:可以不适用Switch组件,此时就不能保证同时只显示一个页面了
    
  • 第二步 确定路由渲染策略

    BrowserRouer基于path策略实现的,类似Vue中的history策略	需要服务器端配合:做重定向。实现的是多页面应用HashRouter基于hash策略实现的	实现的是单页面应用,用路由策略组件渲染应用程序组件
    

路由重定向

路由重定向:在React路由中,通过Redirect组件实现路由重定向

​ from 定义匹配的地址

​ to 定义重定向的地址

默认路由:在React路由中,我们将path属性匹配,既可以定义默认路由。

​ 由于默认路由匹配的很广,因此常常写在最后面。

路由导航:为了切换页面,React路由提供了路由导航组件:Link组件

​ 通过to属性定义切换的地址,即使是hash策略,不要以#开头

​ Link组件只能渲染成a标签。

​ 与a标签相比,Link组件可以适配不同的路由策略

路由数据

通过Route组件渲染页面组件可以通过属性获取路由数据

  • match:包含对路由规则解析的数据,如:path,url,isExact,params(动态路由数据)
  • location:包含当前的真实地址的信息(类似全局的location),如:pathName,search,hash等
  • history:包含对路由操作的方法,(类似全局的history),如:push,replace,go,goBack,goForward等

没有通过Route组件渲染组件不具有路由数据,想获取路由数据,有三种方式

  1. 父子组件通信的方式传递数据可以传递部分数据
  2. 继续使用Route组件渲染该组件
  3. 使用withRouter方法扩展高阶组件
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值