react

1. React简介

  • React 起源于 Facebook 的内部项目,因为该公司对市场上所有 JavaScript MVC 框架,都不满意,就决定自己写一套,用来架设 Instagram(照片交友) 的网站。做出来以后,发现这套东西很好用,就在2013年5月开源了
  • Angular1 2009 年 谷歌 MVC 不支持 组件化开发
  • 由于 React 的设计思想极其独特,属于革命性创新,性能出众,代码逻辑却非常简单。所以,越来越多的人开始关注和使用,认为它可能是将来 Web 开发的主流工具。
  • 清楚两个概念:
    • library(库):小而巧的库,只提供了特定的API;优点就是 船小好掉头,可以很方便的从一个库切换到另外的库;但是代码几乎不会改变;
    • Framework(框架):大而全的是框架;框架提供了一整套的解决方案;所以,如果在项目中间,想切换到另外的框架,是比较困难的;

2. 前端三大主流框架

三大框架一大抄

  • Angular.js:出来较早的前端框架,学习曲线比较陡,NG1学起来比较麻烦,NG2 ~ NG5开始,进行了一系列的改革,也提供了组件化开发的概念;从NG2开始,也支持使用TS(TypeScript)进行编程;
  • Vue.js:最火(关注的人比较多)的一门前端框架,它是中国人开发的,对我我们来说,文档要友好一些;
  • React.js:最流行(用的人比较多)的一门框架,因为它的设计很优秀;

3. React与vue的对比

组件化方面

  1. 什么是模块化:是从代码的角度来进行分析的;把一些可复用的代码,抽离为单个的模块;便于项目的维护和开发;
  2. 什么是组件化: 是从 UI 界面的角度 来进行分析的;把一些可服用的UI元素,抽离为单独的组件;便于项目的维护和开发;
  3. 组件化的好处:随着项目规模的增大,手里的组件越来越多;很方便就能把现有的组件,拼接为一个完整的页面;
  4. Vue是如何实现组件化的: 通过 .vue 文件,来创建对应的组件;
    • template 结构
    • script 行为
    • style 样式
  1. React如何实现组件化:大家注意,React中有组件化的概念,但是,并没有像vue这样的组件模板文件;React中,一切都是以JS来表现的;因此要学习React,JS要合格;ES6 和 ES7 (async 和 await) 要会用;

开发团队方面

  • React是由FaceBook前端官方团队进行维护和更新的;因此,React的维护开发团队,技术实力比较雄厚;
  • Vue:第一版,主要是有作者 尤雨溪 专门进行维护的,当 Vue更新到 2.x 版本后,也有了一个以 尤雨溪 为主导的开源小团队,进行相关的开发和维护;

社区方面

  • 在社区方面,React由于诞生的较早,所以社区比较强大,一些常见的问题、坑、最优解决方案,文档、博客在社区中都是可以很方便就能找到的;
  • Vue是近两年才火起来的,所以,它的社区相对于React来说,要小一些,可能有的一些坑,没人踩过;

移动APP开发体验方面

  • Vue,结合 Weex 这门技术,提供了 迁移到 移动端App开发的体验(Weex,目前只是一个 小的玩具, 并没有很成功的 大案例;)
  • React,结合 ReactNative,也提供了无缝迁移到 移动App的开发体验(RN用的最多,也是最火最流行的);

4. 为什么要学习React

  1. 和Angular1相比,React设计很优秀,一切基于JS并且实现了组件化开发的思想;
  2. 开发团队实力强悍,不必担心断更的情况;
  3. 社区强大,很多问题都能找到对应的解决方案;
  4. 提供了无缝转到 ReactNative 上的开发体验,让我们技术能力得到了拓展;增强了我们的核心竞争力;
  5. 很多企业中,前端项目的技术选型采用的是React.js;

5. React中几个核心的概念

虚拟DOM(Virtual Document Object Model)

  • DOM的本质是什么:浏览器中的概念,用JS对象来表示 页面上的元素,并提供了操作 DOM 对象的API;

  • 什么是React中的虚拟DOM:是框架中的概念,是程序员 用JS对象来模拟 页面上的 DOM 和 DOM嵌套;

  • 为什么要实现虚拟DOM(虚拟DOM的目的):为了实现页面中, DOM 元素的高效更新

  • DOM和虚拟DOM的区别

  • DOM:浏览器中,提供的概念;用JS对象,表示页面上的元素,并提供了操作元素的API;

  • 虚拟DOM:是框架中的概念;而是开发框架的程序员,手动用JS对象来模拟DOM元素和嵌套关系;

    + 本质: 用JS对象,来模拟DOM元素和嵌套关系;
    
    • 目的:就是为了实现页面元素的高效更新;

      虚拟DOM的概念.png

Diff算法

  • tree diff:新旧两棵DOM树,逐层对比的过程,就是 Tree Diff; 当整颗DOM逐层对比完毕,则所有需要被按需更新的元素,必然能够找到;

  • component diff:在进行Tree Diff的时候,每一层中,组件级别的对比,叫做 Component Diff;

    • 如果对比前后,组件的类型相同,则暂时认为此组件不需要被更新;
    • 如果对比前后,组件类型不同,则需要移除旧组件,创建新组件,并追加到页面上;
  • element diff:在进行组件对比的时候,如果两个组件类型相同,则需要进行 元素级别的对比,这叫做 Element Diff;

    [图片上传失败...(image-19dc01-1595464286977)]

6. 创建项目

第一种方式

全局安装create-react-app
npm install -g create-react-app

创建项目
create-react-app 项目名

第二种方式

npx —— 它是 [npm 5.2+ 附带的 package 运行工具],这种方式会先局部安装脚手架,然后在安装项目

npx create-react-app your-app

项目安装这需要等待一段时间,这个过程实际上会安装三个东西

  • react: react的顶级库,专门用于创建组件和虚拟DOM的,同时组件的生命周期都在这个包中
  • react-dom:专门进行DOM操作的,最主要的应用场景,就是ReactDOM.render()
  • react-scripts: 包含运行和打包react应用程序的所有脚本及配置

7. 创建虚拟dom并渲染

1.创建虚拟DOM元素:

// 这是 创建虚拟DOM元素的 API    

你比四环多一环

// 第一个参数: 字符串类型的参数,表示要创建的标签的名称 // 第二个参数:对象类型的参数, 表示 创建的元素的属性节点 // 第三个参数: 子节点 const myh1 = React.createElement('h1', { title: '啊,五环', id: 'myh1' }, '你比四环多一环')

2.渲染:

  // 3. 渲染虚拟DOM元素
  // 参数1: 表示要渲染的虚拟DOM对象
  // 参数2: 指定容器,注意:这里不能直接放 容器元素的Id字符串,需要放一个容器的DOM对象
  ReactDOM.render(myh1, document.getElementById('app'))
注:像这样创建虚拟dom太麻烦了,
React.createElement('h1', { title: '啊,五环', id: 'myh1' }, '你比四环多一环')
所以有了jsx 语法,写jsx语法,webpack的babel-loader会转化为这样
React.createElement('h1', { title: '啊,五环', id: 'myh1' }, '你比四环多一环')

8. JSX语法

  • 什么是JSX语法:

    就是符合 xml 规范的 JS 语法;(语法格式相对来说,要比HTML严谨很多)

    REACT独有的语法 JAVASCRIPT+XML(HTML)

    和我们之前自己拼接的HTML字符串类似,都是把HTML结构代码和JS代码或者数据混合在一起了,

    但是它不是字符串

下面这些安装,如果用脚手架创建的项目,脚手架已经做了,不用安装了

  1. 如何启用 jsx 语法?
  • 安装 babel 插件

    • 运行cnpm i babel-core babel-loader babel-plugin-transform-runtime -D
    • 运行cnpm i babel-preset-env babel-preset-stage-0 -D
  • 安装能够识别转换jsx语法的包 babel-preset-react

    • 运行cnpm i babel-preset-react -D
  • 添加 .babelrc 配置文件

    {
      "presets": ["env", "stage-0", "react"],
      "plugins": ["transform-runtime"]
    }
    
  • 添加babel-loader配置项:

    module: { //要打包的第三方模块
        rules: [
          { test: /\.js|jsx$/, use: 'babel-loader', exclude: /node_modules/ }
        ]
    }
    
  1. jsx 语法的本质:并不是直接把 jsx 渲染到页面上,而是 内部先转换成了 createElement 形式,再渲染的;

  2. 在 jsx 中混合写入 js 表达式:在 jsx 语法中,要把 JS代码写到 { } 中,但是要求JS代码指执行完成有返回结果(JS表达式)

    • 渲染数字
    • 渲染字符串
    • 渲染布尔值
    • 为属性绑定值
    • 渲染jsx元素
    • 渲染jsx元素数组
  3. 在 jsx 中 写注释:推荐使用{ /* 这是注释 */ }

  4. 为 jsx 中的元素添加class类名:需要使用className 来替代 classhtmlFor替换label的for属性

  5. 在JSX创建DOM的时候,所有的节点,必须有唯一的根元素进行包裹;

  6. 在 jsx 语法中,标签必须 成对出现,如果是单标签,则必须自闭和!

当 编译引擎,在编译JSX代码的时候,如果遇到了<那么就把它当作 HTML代码去编译,

如果遇到了 {} 就把 花括号内部的代码当作 普通JS代码去编译;

9. React中创建组件

  • 不管是VUE还是REACT框架,设计之初都是期望我们按照“组件/模块管理”的方式来构建程序的,也就是把一个程序划分为一个个的组件来单独处理

  • 优势
    1.有助于多人协作开发
    2.我们开发的组件可以被复用

  • REACT中创建组件有两种方式:
    函数声明式组件
    基于继承COMPONENT类来创建组件

  • SRC -> COMPONENT :这个文件夹中存放的就是开发的组件

  • 返回的jsx,必须是唯一的节点包裹,如果不想让这个节点展示,可以用这个包裹 <>

    hhh

hhh

第1种 - 函数声明式组件(无状态组件)

函数声明式组件,如果要接收外界传递的数据,需要在 构造函数的参数列表中使用props来接收;

必须要向外return一个合法的JSX;

  • 创建组件:

    //Dialog.js
    import React from 'react';
    //=>每一个组件中都要导入REACT,因为需要基于它的CREATE-ELEMENT把JSX进行解析渲染呢
    
    /*
     * 函数式声明组件
     *   1.函数返回结果是一个新的JSX(也就是当前组件的JSX结构)
     *   2.PROPS变量存储的值是一个对象,包含了调取组件时候传递的属性值(不传递是一个空对象)
     *   3.children 代表双闭合组件中的子元素,相当于vue中插槽的概念
     *   
     */
    export default function Dialog(props) {//props形参,接收外界传递过来的数据,只读的;不能被重新赋值;
        let {con, lx = 0, style = {},children} = props,
            
          //=>children(代表双闭合组件中的子元素)
        return 

    {title}

    {con}
    {children}
    ; };
  • 使用组件,并为组件传递数据:

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

import Dialog from './component/Dialog'; //引入组件,组件的名称首字母必须是大写

ReactDOM.render(


{/注释:JSX中调取组件,只需要把组件当做一个标签调取使用即可(单闭合和双闭合都可以)/}

 {/*属性值不是字符串,我们需要使用大括号包起来*/}
  
      1  //这个双闭合标签里面的内容就是用children接受
      2

, root);



### 第2种 - 创建类式(有状态组件)

>必须继承 React.Component 和有一个render函数并且返回jsx

* 创建组件

```js
import React from 'react'

export default class Dialog extends React.Component {
      render() {
       //“this.props组件的属性是只读的”
        return 

{this.props.lx}

{this.props.con}
; } }
  • 使用组件

    import React from 'react';
    import ReactDOM from 'react-dom';
    import Dialog from './01-component/Dialog' //引入组件
    
     
    ReactDOM.render(
      , //使用组件
      document.getElementById('root')
    );
    

两种创建组件方式的对比

  1. 函数创建出来的组件:叫做“无状态组件”
  2. class关键字创建出来的组件:叫做“有状态组件”
  1. 用函数**创建出来的组件:叫做“无状态组件”【无状态组件今后用的不多】
  2. class关键字创建出来的组件:叫做“有状态组件”【今后用的最多】
  3. 什么情况下使用有状态组件?什么情况下使用无状态组件?
    • 如果一个组件需要有自己的私有数据,则推荐使用:class创建的有状态组件;
    • 如果一个组件不需要有私有的数据,则推荐使用:无状态组件;
    • React官方说:无状态组件,由于没有自己的state和生命周期函数,所以运行效率会比 有状态组件稍微高一些;

有状态组件和无状态组件之间的本质区别就是:有无state属性、和 有无生命周期函数;

10.组件的数据挂载方式

属性(props)

props是正常是外部传入的,属性不能被组件自己更改,组件内部也可以设置默认值,字符类型,

属性是描述性质、特点的,组件自己不能随意更改。

在使用一个组件的时候,可以把参数放在标签的属性当中,所有的属性都会作为组件 props 对象的键值。通过箭头函数创建的组件,需要通过函数的参数来接收props

给属性设置规则,需要安装第三方插件

yarn add prop-types

给属性设置默认值和规则

import React from 'react'
import PropTypes from 'prop-types';
export default class Dialog extends React.Component {
    //THIS.PROPS是只读的,我们无法在方法中修改它的值,但是可以给其设置默认值 
    static defaultProps = {
        lx: '系统提示'
    };

    //PROP-TYPES是FACEBOOK公司开发的一个插件
     基于这个插件我们可以给组件传递的属性设置规则
    static propTypes = {
         //=>不仅传递的内容是字符串,并且还必须传递
        con: PropTypes.string.isRequired, 

        //=>传递的内容需要是数字
        lx: PropTypes.number,

         //=>传递的内容需要是数组
        city:PropTypes.array,

        //=>数组中的每一项需要是字符串
        list:PropTypes.arrayOf(PropTypes.string),

        //=>自定义函数验证
        address:function(props,propname){
            if(props[propname] !=='aa'){
                return new Error('内容非法')
            }
            //console.log(arguments);
        }
    };

    render() {
       //“this.props组件的属性是只读的”
        return 

{this.props.lx}

{this.props.con}
; } }

状态state

  • react中的state相当于vue中的data属性

  • state顾名思义就是状态,它只是用来控制这个组件本身自己的状态,我们可以用state来完成对行为的控制、数据的更新、界面的渲染,由于组件不能修改传入的props,所以需要记录自身的数据变化。

  • 更改状态,只能用 setState()方法,当我们调用这个函数的时候,React.js 会更新组件的状态 state ,并且重新调用 render 方法,然后再把 render 方法所渲染的最新的内容显示到页面上

  • setState 本身在生命周期函数或者在合成事件中执行是异步的

在保证生命周期函数执行的顺序不紊乱

保证其实现渲染队列的机制(可以合并setState统一渲染)

在原生的事件绑定中和其他异步操作中的set-state是同步的,没有渲染队列效果了

import React, { Component } from 'react'

export default class movie extends Component {
    constructor(){
        super()
       //state定义的第一种方式
      //设置状态
        this.state = {
            title:"中国机长"
        }
    }
  
  //state定义的第二种方式
 // state ={
//   title:"中国机长",
//   count:1
// }
  
    componentDidMount(){
      //更改状态,因为是异步的,想立即获取更改后的内容,可以用第2个参数:回调函数
      //更改state第一种方式
        this.setState({
            title:"战狼"
        },() => {
          //相当于vue $nextTick
            console.log(this.state.title);
        }
        )
        
      //更改state的第二种方式
      //this.state.title="战狼"
      //this.setState({})
    }
    render() {
        return (
            
{this.state.title}
) } }

组件中的 propsstate/data 之间的区别

  • props 中的数据都是外界传递过来的;
  • state/data 中的数据,都是组件私有的;(通过 Ajax 获取回来的数据,一般都是私有数据);
  • props 中的数据都是只读的;不能重新赋值;
  • state/data 中的数据,都是可读可写的;

状态提升

如果有多个组件共享一个数据,把这个数据放到共同的父级组件中来管理

受控组件与非受控组件

  • 受控组件

    • 其实就相当于vue中的v-model指令,在react中是自己手动实现,用onChange事件

    • 双向数据绑定就是受控组件

    • 受控组件就是可以被 react 状态控制的组件
      在 react 中,Input textarea 等组件默认是非受控组件(输入框内部的值是用户控制,和React无关)。

      但是也可以转化成受控组件,就是通过 onChange 事件获取当前输入内容,将当前输入内容作为 value 传入,此时就成为受控组件
      好处:可以通过 onChange 事件控制用户输入,过滤不合理输入。

    image.png
    • import React, { Component} from 'react';
      
      export default class Form extends Component {
        constructor() {
          super();
          this.state = {
            name: "",
            desc: "",
          }
           
        }
      
        handleChange(event) {
          this.setState({
            [event.target.name]: event.target.value //把表单中的值赋值给状态
          })
        }
       
      
        render() {
          return (
            
      //表单中的value,绑定了state上的状态值
      ); } }
  • 非受控组件

    • 不受状态控制的就是非受控组件

    • 基于REF操作DOM实现视图更新的,叫做“非受控组件”

    • 使用场景,必须手动操作DOM元素,setState 实现不了的,例如文件上传

    • import React,{Component} from 'react';
      import ReactDOM from 'react-dom';
      
      export default class Sum extends Component{
          handleChange=(event)=>{
              let a = parseInt(this.refs.a.value||0);
              let b = parseInt(this.refs.b.value||0);
              this.refs.result.value = a+b;
          }
          render(){
              return (
                  //经过React封装可以onChange可以写在div上
                  
      + =
      //input是非受控组件,因为不受状态控制 ) } }

11.组件中DOM样式

  • 行内

    import React from 'react'
     
    export default class Movie extends React.Component {
        render() {
            return 
    {/* 行内样式 */}

    正在热映

    } }
  • 外部引入

    这种引入方式是全局生效

    import React from 'react'
    import './Movie.css' //从外部引入
     
    
    export default class Movie extends React.Component {
        render() {
            return 

    正在热映

    } }
  • 模块化

    这种引入方式是局部生效,目的解决全局冲突

    • Vue 组件中的样式表,有没有 冲突的问题???

      Vue 组件中的样式表,也有冲突的问题;但是,可以使用

    • React 中,有没有类似于 scoped 这样的指令呢?

    ​ 没有;因为 在 React 中,根本就没有指令的概念;

    ​ 模块块就是为了解决冲突的问题,局部生效

    • 文件名必须是 *.module.css结尾,同样的,如是要是.sass.scss的话,文件名格式应该是[name].module.sass[name].module.scss

    • 其实模块化,就是webpack给css-loader配置了参数modules

     module: {  
        rules: [ 
          // 其中,有个固定的参数,叫做 modules , 表示为 普通的 CSS 样式表,启用模块化
          { test: /\.scss$/, use: ['style-loader', 'css-loader?modules', 'sass-loader'] }  
        ]
      }
    
    import React from 'react'
    import common from "./common.module.css"  //引入css文件
    
    export default class Movie extends React.Component {
       render() {
           return 

    //使用css gggg

    } }
    //common.module.css 文件
    .error{
       color:red;
       font-size: 30px;
    }
    
  • 不同的条件添加不同的样式

    • 安装
    yarn add classnames
    
    • 特点就是根据动态数据控制样式

    • 局部,不会全局污染

    代码:

    import React  from 'react'
    import classNames from 'classnames/bind' //引入第三方插件
    import styles from './styles.css'       //引入样式
    
    let cx = classNames.bind(styles) //绑定在一块
    export default class profile extends React.Component{
       render(){
           let names = cx({
               inProcess:true,  //根据数据来决定使用哪一个样式
               error:false
           })
           return 

    gp18

    } }
    //styles.css文件
    .inProcess {
     color: orange;
     font-size: 30px;
    }
    
    .error {
     color: red;
     font-size: 30px;
    }
    
  • css-in-js

    • styled-components`是针对React写的一套css-in-js框架,简单来讲就是在js中写css

    • 解决全局污染

    • 安装

    yarn add styled-components
    

    代码:

    import React from 'react'
    
    import {Wrap} from './search-style' //引入样式
    export default class Search extends  React.Component{
        render(){
            return   //使用
                yyyy
                

    hhh

    } }
    //search-style.js 文件 
    import React from 'react'
    import styled from 'styled-components' //引入第三方插件
    
    const Wrap = styled.div`
        width:500px;
        height:500px;
        background:${(props)=>props.color}; //动态赋值
        font-size:30px;
        h1 {
          font-size:50px;
        }
    `
    export {
        Wrap
    }
    

12.事件处理

  • 采用on+事件名的方式来绑定一个事件,注意,这里和原生的事件是有区别的,原生的事件全是小写onclick, React里的事件是驼峰onClickReact的事件并不是原生事件,而是合成事件

  • Event对象

    和普通浏览器一样,事件handler会被自动传入一个 event 对象,这个对象和普通的浏览器 event 对象所包含的方法和属性都基本一致。不同的是 React中的 event 对象并不是浏览器提供的,而是它自己内部所构建的。它同样具有event.stopPropagationevent.preventDefault 这种常用的方法

  • 事件的几种写法,目的能访问到this

    • 第一种写法 (不能传递参数)
    import React, { Component } from 'react'
    
    export default class event1 extends Component {
        constructor() {
            super()
            this.state = {
                name: 'gp18'
            }
        }
    
        handleClick=(event) => { //箭头函数
            console.log(this.state.name);
        }
    
        render() {
            return (
                
    /*这种绑定事件的方法,要想this能访问到, handleClick函数必须用箭头函数 */
    ) } }
    • 第二种写法 (不能传递参数)
    import React, { Component } from 'react'
    
    export default class event1 extends Component {
        constructor() {
            super()
            this.state = {
                name: 'gp18'
            }
          
          //@key
            this.myhandleclick = this.handleClick.bind(this)
        }
    
        //@key 最后追加一个参数,即可接受event
        handleClick(val, event) {
            console.log(val, event, this.state.name);
        }
    
        render() {
            return (
                
    ) } }
    • 第三种写法 (可以传递参数)
    import React, { Component } from 'react'
    export default class event1 extends Component {
        constructor() {
            super()
            this.state = {name: 'gp18'}
        }
    
       //触发函数
        handleClick(val,event){
            console.log(this.state.name,val,event);
        }
    
        render() {
            return (
                
    //因为handleClick函数不是箭头函数,需要改变this指向
    ) } }
    • 第四种写法 (可以传递参数)
    import React, { Component } from 'react'
    export default class event1 extends Component {
        constructor() {
            super()
            this.state = {name: 'gp18'}
        }
    
        handleClick(val,event){
            console.log(this.state.name,val,event);
        }
    
        render() {
            return (
                
    ) } }

13. 组件生命周期

  • 生命周期的概念

每个组件的实例,从创建、到运行、直到销毁,在这个过程 中,会出发一些列 事件,这些事件就叫做组件的生命周期函数;

  • 生命周期分为三部分:

  • 组件创建阶段

组件创建阶段的生命周期函数,有一个显著的特点:创建阶段的生命周期函数,在组件的一辈子中,只执行一次;

  • componentWillMount

​ 组件将要被挂载,此时还没有开始渲染虚拟DOM

  • render:

​ 第一次开始渲染真正的虚拟DOM,当render执行完,

​ 内存 中就有了完整的虚拟DOM了

  • componentDidMount

​ 组件完成了挂载,此时,组件已经显示到了页面上,

​ 当这个方法执行完,组件就进入都了 运行中 的状态

​ 如果要初始化第三方的dom库,也在这里进行初始化。只有到这里才能获取到真实的dom.

通常在这里进行ajax请求

  • 组件运行阶段

也有一个显著的特点,根据组件的state和props的改变,有选择性的触发0次或多次;

  • componentWillReceiveProps:

​ 组件将要接收新属性,此时,只要这个方法被触发,

​ 就证明父组件为当前子组件传递了新的属性值;

         props改变的时候
  • shouldComponentUpdate:

​ 组件是否需要被更新,此时,组件尚未被更新,

​ 但是,state 和 props 肯定是最新的

​ 在渲染新的propsstate前,shouldComponentUpdate会被调用。默认为true

​ 这个方法不会在初始化时被调用,也不会在forceUpdate()时被调用。

​ 返回false不会阻止子组件在state更改时重新渲染。

​ 如果shouldComponentUpdate()返回falsecomponentWillUpdate,rendercomponentDidUpdate不会被调用。

  • componentWillUpdate:

​ 组件将要被更新,此时,尚未开始更新,

​ 内存中的虚拟DOM 树还是旧的

  • render:

​ 此时,又要重新根据最新的 state 和 props

               重新渲染一棵内存中的 虚拟DOM树,当 render 调用完毕,内存中的旧DOM树,

​ 已经被新DOM树替换了!此时页面还是旧的

  • componentDidUpdate

​ 此时,页面又被重新渲染了,state 和 虚拟DOM 和页面已经 完全保持同步

  • 组件销毁阶段

也有一个显著的特点,一辈子只执行一次;

  • componentWillUnmount:

​ 组件将要被卸载,此时组件还可以正常使用;

​ 在组件被卸载并销毁之前立即被调用。在此方法中执行任何必要的清理,

​ 例如使定时器无效,取消网络请求或清理在componentDidMount中创建的任何监听

  • 生命周期图

    react_第1张图片
    image.png

新版本v16.4 的生命周期函数

  • v17版本

componentWillMount,componentWillReceiveProps,componentWillUpdate这三个函数将要作废

  • v16.4新增的生命周期函数

  • getDerivedStateFromProps

    //可以拿到父组件传递过来的属性,同时可以拿到当前组件的state
    //可以把传递过来的属性合并到当前组件state上
      static getDerivedStateFromProps(props, state) {
        console.log('getDerivedStateFromProps:', props, state)
        return {
          ...props,
          ...state
        }
      }
    
  • getSnapshotBeforeUpdate()

    在react render()后的输出被渲染到DOM之前被调用。它使您的组件能够在它们被潜在更改之前捕获当前值(如滚动位置)。这个生命周期返回的任何值都将作为参数传componentDidUpdate()

    只要写这个生命周期函数就必须写componentDidUpdate

    getSnapshotBeforeUpdate(prevProps, prevState) {
        console.log("getSnapshotBeforeUpdate:", prevProps, prevState)
        return {
          username:'hanye',
          age:20
        };
      }
    
    componentDidUpdate(prevProps, prevState,sanpshot){
        console.log("componentDidUpdate:",prevProps, prevState,sanpshot)
      }
    
image.png

PureComponent

PureComponnet里如果接收到的新属性或者是更改后的状态和原属性、原状态相同的话,就不会去重新render了
在里面也可以使用shouldComponentUpdate,而且。是否重新渲染以shouldComponentUpdate的返回值为最终的决定因素。此方法仅作为性能优化的方式而存在。不要企图依靠此方法来“阻止”渲染,因为这可能会产生 bug。你应该**考虑使用内置的 [PureComponent](当 props 或 state 发生变化时,shouldComponentUpdate() 会在渲染执行之前被调用。返回值默认为 true。首次渲染或使用 forceUpdate() 时不会调用该方法。)

import React, { Component,PureComponent } from 'react';
export default class Parent extends PureComponent { //@Key

 constructor() {
   super();
   this.state = { count: 1,}
  
 }

 render() {
   return (
     
); } handleClick = () => { this.state.count = 100; this.forceUpdate(); //@key } }

14 HOC高阶组件

  • 定义

具体而言,高阶组件是参数为组件,返回值为新组件的函数

const EnhancedComponent = higherOrderComponent(WrappedComponent);
  • 作用

在多个不同的组件中需要用到相同的功能,这个解决方法,通常是高阶组件。

  • 例子
//定义一个高阶组件 Hoc.js
import React, { Component } from 'react'
const Hoc = (WrapperCommonent)=>{
    return class Copyright extends Component {
        render() {
            return (
                
版权所有
) } } } export default Hoc //使用高阶组件 import React, { Component } from 'react' import Hoc from './Hoc' class Base extends Component { render() { return (
react.js 是一个构建用户节目的库
) } } export default Hoc(base) //@Key

15.组件通信

父组件与子组件通信

父组件将自己的状态传递给子组件,子组件当做属性来接收,当父组件更改自己状态的时候,子组件接收到的属性就会发生改变

//父组件
import React, { Component } from 'react'
import Child from './Child'
export default class Parent extends Component {
   constructor(){
       super()
       this.state = {count:1}
   }
   render() {
       return (
          //状态传递给子组件
       )
   }
}
//子组件
import React, {Component} from 'react'

export default class Child extends Component {
   render() {
       return  
{this.props.count} //子组件当做属性来接收
} }

子组件与父组件通信

父组件将自己的某个方法传递给子组件,在方法里可以做任意操作,比如可以更改状态,子组件通过this.props接收到父组件的方法后调用。

//父组件
import React, { Component } from 'react'
import Child from './Child'
export default class Parent extends Component {
   constructor(){
       super()
       this.state = {count:1}
   }

   handclick=()=>{
       this.setState({
           count:10
       })
   }
   render() {
       return (
         
{this.state.count} //将方法传递给子组件
) } } //子组件 import React, {Component} from 'react' export default class Child extends Component { componentDidMount(){ this.props.fn() } render() { return
child
} }

跨组件通信 context

在react没有类似vue中的事件总线来解决这个问题,我们只能借助它们共同的父级组件来实现,将非父子关系装换成多维度的父子关系。react提供了context api来实现跨组件通信, React 16.3之后的contextapi较之前的好用。

在父组件上提供数据,其他后代组件,都能访问到该数据

举个计数器的例子,这个例子是context在实际项目这样写,具体基础的看文档

//Mycontext.js
import React, { Component,createContext} from 'react'
const {Provider,Consumer:MyConsumer} = createContext()

class MyProvider extends Component {
   constructor(){
       super()
       this.state = {count:1}
   }

   incr=()=>{
       this.setState({
           count:this.state.count+1
       })
   }

   render() {
       return ( //需要共享的数据写在value里
           
               {this.props.children}
           
       )
   }
}
export {MyProvider,MyConsumer}

//父组件 Parent.js
import React, { Component } from 'react'
import Child from './Child'

import {MyProvider} from './Mycontext'
export default class Parent extends Component {
   render() {
       return (
           
               
           
       )
   }
}
//后代组件 Child.js
import React, { Component } from 'react'
import {MyConsumer} from './Mycontext'

export default class Button extends Component {
   render() {
       return (//后代访问共享数据
           
               {
                   ({incr,count})=>{
                   return  
                   }
               }
           
       )
   }
}

redux

  • 什么是redux?

    进行状态统一管理的类库(适应于任何技术体系的项目)

1.只要两个或多个组件之间想要实现信息的共享,都可以基于redux解决,把共享的信息存储到redux容器进行管理

2.还可以做临时存储,页面加载的时候,把从服务器获取的数据信息存储到redux中

  • 为什么要用redux

因为对于react来说,同级组件之间的通信尤为麻烦,或者是非常麻烦了,所以我们把所有需要多个组件使用的state拿出来,整合到顶部容器,进行分发

  • 需要使用redux的项目

  • 用户的使用方式复杂
  • 不同身份的用户有不同的使用方式(比如普通用户和管理员)
  • 多个用户之间可以协作
  • 与服务器大量交互,或者使用了WebSocket
  • View要从多个来源获取数据
  • 从组件层面考虑,什么样子的需要Redux:

  • 某个组件的状态,需要共享
  • 某个状态需要在任何地方都可以拿到
  • 一个组件需要改变全局状态
  • 一个组件需要改变另一个组件的状态
  • Redux的设计思想

  1. Web 应用是一个状态机,视图与状态是一一对应的。
  2. 所有的状态,保存在一个对象里面(唯一数据源)
  • Redux的使用的三大原则

  • Single Source of Truth(唯一的数据源)
  • State is read-only(状态是只读的)
  • Changes are made with pure function(数据的改变必须通过纯函数完成)
  • Redux的流程

image.png
  • Store的角色是整个应用的数据存储中心,集中大部分页面需要的状态数据;
    ActionCreators ,view 层与data层的介质;
    Reduce ,接收action并更新Store。
    所以流程是 用户通过界面组件 触发ActionCreator,携带Store中的旧State与Action 流向Reducer,Reducer返回新的state,并更新界面。

  • 自己实现的redux




    
    

react-redux

  • 安装

yarn add redux react-redux
yarn add redux-thunk //异步请求数据要用到这个插件
  • redux

适应于所有项目,store = createStore(reducer),然后在需要的地方通过store.getState()去获取数据,通过store.dispatch去更新数据,通过store.subscribe去订阅数据变化然后进行setState...如果很多地方都这样做一遍,实在是不堪其重

  • REACT-REDUX

  • 描述

​ react-redux只是适应react项目

​ 是把REDUX进一步封装,适配REACT项目,让REDUX操作更简洁

​ STORE文件夹中的内容和REDUX一模一样

​ 在组件调取使用的时候可以优化一些步骤

  • 相对于传统的REDUX,我们做的步骤优化

​ 导出的不在是我们创建的组件,而是基于CONNECT构造后的高阶组件

​ export default connect([mapStateToProps], [mapDispatchToProps])([自己创建的组件]);

​ REACT-REDUX帮我们做了一件非常重要的事情:

​ 以前我们需要自己基于SUBSCRIBE向事件池追 加方法,以达到容器状态信息改变,

​ 执行我们追加的方法,重新渲染组件的目的,但是现在不用了,

​ REACT-REDUX帮我们做了这件事:“所有用到REDUX容器状态信息的组件,都会向事件池 中追加一个方法,当状态信息改变,通知方法执行,

​ 把最新的状态信息作为属性传递给组件,组件的属性值改变了,组件也会重新渲染”

  • react-redux 其实就提供了这四个东西

  • Provider 根组件

当前整个项目都在Provider组件下

作用就是把创建的STORE可以供内部任何后代组件使用(基于上下文完成的)

=>Provider组件中只允许出现一个子元素

=>把创建的STORE基于属性传递给Provider(这样后代组件中都可以使用这个STORE了)

redux的时候,每个组件想使用store,属性都得传store

这个函数的底层实现原理

​ 其实就是根据上下文实现的

​ PROVIDER:当前项目的“根”组件

​ 接收通过属性传递进来的STORE,把STORE挂载到上下文中,

​ 这样当前项目中任何一个组件中,想要使用REDUX中的STORE,

​ 直接通过上下文获取即可

class Provider extends React.Component {
   //=>设置上下文信息类型
   static childContextTypes = {
       store: PropTypes.object
   };

   //=>设置上下文信息值
   getChildContext() {
       return {
           store: this.props.store
       };
   }

   constructor(props, context) {
       super(props, context);
   }

   render() {
       return this.props.children;
   }
}
  • connect 高阶组件

CONNECT:高阶组件(基于高阶函数:柯理化函数)创建的组件就是高阶组件
@PARAMS
   mapStateToProps:回调函数,把REDUX中的部分状态信息挂载到指定组件的属性上
   function mapStateToProps(state){
        //=>state:REDUX容器中的状态信息
      return {};  //=>RETURN对象中有啥,就把啥挂载到属性上
        }
      ```
      
    mapDispatchToProps:回调函数,把一些需要派发的任务方法也挂载到组件的属性上
     ```
   function mapDispatchToProps(dispatch){
        //=>dispatch:store中的dispatch
          return {
                init(){
                   dispatch({...});
                }
           };
           //=>RETURN啥就把啥挂载到属性上(返回的方法中有执行dispatch派发任务的操作)
        }
      ```

  @RETURN
   返回一个新的函数 CONNECT-HOT

====

CONNECT-HOT
@PARAMS
传递进来的是要操作的组件,我们需要把指定的属性和方法都挂载到当前组件的属性上

@RETURN
返回一个新的组件Proxy(代理组件),在代理组件中,我们要获取Provider在上下文中存储的store,紧接着获取store中的state和dispatch,把mapStateToProps、mapDispatchToProps回调函数执行,接收返回的结果,在把这些结果挂载到Component这个要操作组件的属性上

function connect(mapStateToProps, mapDispatchToProps) {
return function connectHOT(Component) {
return class Proxy extends React.Component {
//=>获取上下文中的STORE
static contextTypes = {
store: PropTypes.object
};

         constructor(props, context) {
             super(props, context);
        this.state = this.queryMountProps();
         }
     
         //=>基于REDUX中的SUBSCRIBE向事件池中追加一个方法,当容器中状态改变,我们需要重新获取最新的状态信息,并且重新把COMPONENT渲染,把最新的状态信息通过属性传递给COMPONENT
         componentDidMount() {
            this.context.store.subscribe(() => {
                 this.setState(this.queryMountProps());
             });
     }
     
         //=>渲染COMPONENT组件,并且把获取的信息(状态、方法)挂载到组件属性上(单独调取POXY组件的是时候传递的属性也给COMPONENT)
         render() {
             return 
         }
    
         //=>从REDUX中获取最新的信息,基于回调函数筛选,返回的是需要挂载到组件属性上的信息
         queryMountProps = () => {
             let {store} = this.context,
                 state = store.getState();
             let propsState = typeof mapStateToProps === 'function' ? mapStateToProps(state) : {};
            let propsDispatch = typeof mapDispatchToProps === 'function' ? mapDispatchToProps(store.dispatch) : {};
     
             return {
                 ...propsState,
                 ...propsDispatch
             };
         };
     }
}

}


  • mapStateToProps函数
let mapStateToProps = state => {

  //=>state:就是REDUX容器中的状态信息

  //=>我们返回的是啥,就把它挂载到当前组件的属性上
  //=>(REDUX存储很多信息,我们想用啥就返回啥即 可)
  return {
     ...state.vote
  };

};
  • mapDispatchToProps
//=>把REDUX中的DISPATCH派发行为遍历,也赋值给组件的属性(ActionCreator)
let mapDispatchToProps = dispatch => {
   //=>dispatch:STORE中存储的DISPATCH方法
   //=>返回的是啥,就相当于把啥挂载到组件的属性上
   //(一般我们挂载一些方法,这些方法中完成了DISPATCH派发任务操作)
   return {
       init(initData) {
           //action.vote.init(initData)返回的就是类似{type:'aa',...}
           dispatch(action.vote.init(initData));
       }
   };
};

export default connect(state => ({...state.vote}), action.vote)(VoteBase);//=>REACT-REDUX帮我们做了一件事情,把ACTION-CREATOR中编写的方法(返回ACTION对象的方法),自动构建成DISPATCH派发任务的方法,也就是mapDispatchToProps这种格式
  • 具体使用

  • 目录

    store

​ reducer 存放每一个模块的reducer

​ vote.js

​ personal.js

​ ...

​ index.js 把每一个模块的reducer最后合并成为一个reducer

​ action 存放每一个模块需要进行的派发任务(ActionCreator)

​ vote.js

​ personal.js

​ ...

​ index.js 所有模块的ACTION进行合并

​ action-types.js 所有派发任务的行为标识都在这里进行宏观管理

​ index.js 创建STORE

  • 代码

  • reducer目录的代码

    image.png
  • action目录的代码

    aa.jpeg
  • action-types.js 文件

    image.png
  • index.js 文件

    image.png
  • 使用

  • 在入口index.js提供数据store

image.png
  • 在子组件消费数据
image.png

16 路由

  • 安装

    react-router-dom
    
  • 第一个例子:基本路由

    • 基本使用

    • 路由跳转

    • 匹配不到返回404

    • 两种方式的传参及接受参数

    • 使用component属性渲染组件

    import React, { Component } from 'react'
    import {BrowserRouter as Router,Route,Link,useLocation,Redirect,Switch} from 'react-router-dom'
    
    function Home(props) {
        //获取参数
        let result = new URLSearchParams(useLocation().search)
        console.log(result.get("city"));
        
        return 

    Home

    } function About(props) { //获取参数 console.log(props.match.params.id); return

    About

    ; } export default class index extends Component { render() { return ( //必须用Router包裹 首页  关于  我的
    //Switch:排他性,只能匹配到一个路由 我的 404
    ) } }
  • 第二例子 嵌套路由

    //一级路由
    import React, { Component } from 'react'
    import {BrowserRouter as Router,Route,Link,useLocation,Redirect,Switch} from 'react-router-dom'
    
    import Order from './Order'
    export default class index extends Component {
        render() {
            return (
                
                 订单
     
                  
                    
                
            )
        }
    }
    
    //二级路由 Order.js
    import React, { Component } from 'react'
    
    import  {Link,Route,Switch} from 'react-router-dom'
    export default class Movie extends Component {
        render() {
            return (
                
    已支付 未支付
    pay unpay
    ) } }
  • 第三个例子 render

    • 用render函数渲染组件

    • 可以根据逻辑,来判断渲染哪一个组件

    import React, { Component } from 'react'
    import {BrowserRouter as Router,Route,Switch,Link} from 'react-router-dom'
    
    function Login(props) {
        return 

    Login

    ; } function Pos(props) { return

    Pos

    ; } export default class index extends Component { constructor(){ super() this.state = {isLogin:false} } render() { return ( 首页  职位管理 index { return this.state.isLogin ? : }}> ) } }
  • 第四例子 NavLink

    • 一般用NavLink比较多
    • 点中哪个链接,自动给加个 class="active"
    • Link 标签不会自动加的
    import React, { Component } from 'react'
    import {BrowserRouter as Router,Route,NavLink,useLocation,Redirect,Switch} from 'react-router-dom'
    
    export default class index extends Component {
        render() {
            return (
                  
                    首页 
                    关于 
                    
    index about
    ) } }
  • 第五个例子 把a链接换成其他标签

    • NavLink /Link 生成的是a链接,在外边在用li标签包裹一下怎么做呢
    import React, { Component } from 'react'
    import {BrowserRouter as Router,Route,NavLink} from 'react-router-dom'
    
     //自定义链接
    const CustomLink = (props)=>{
      return 
  • {props.name}
  • } export default class index extends Component { render() { return ( 电影 
    home movie
    ) } }
  • 第六个例子 编程式导航

    • 不像vue那么简单,一个函数搞定
    • 这个是自己实现的
    import React, { Component } from 'react'
    import './style.css'  //这个就是定义激活active的样式
    import {BrowserRouter as Router,Route,Link,NavLink,Redirect,Switch} from 'react-router-dom'
    
    import City from './City' //这个就是自己定义的编程式导航
    export default class index extends Component {
        
        render() {
            return (
                
                        我的  
                        
                    
    profile City
    ) } } //自己定义的编程式导航 City.js import React, { Component } from 'react' import {withRouter} from 'react-router-dom' class City extends Component { handleClick=()=>{ //编程式导航 this.props.history.push(this.props.path) } render() { return (
  • 城市
  • ) } } //自定义链接是拿不到路由信息, 这个必须用withRouter这个高阶函数增强一下,才能拿到路由相关的信息 export default withRouter(City)
  • 第七个例子 用children函数来渲染组件

    • 不管路由匹配上或者匹配不上,总会执行它

    • 只不过区别是匹配上 props.match 不为空,否则为空

    import React, { Component } from 'react'
    import './style.css'
    import {BrowserRouter as Router,Route,NavLink} from 'react-router-dom'
     
    export default class index extends Component {
        
        render() {
            return (
                
                        我的  
                        {
                            return 设置
                        }}>
    
                    
    profile setting
    ) } }

安装 React Developer Tools 调试工具

React Developer Tools - Chrome 扩展下载安装地址

总结

理解React中虚拟DOM的概念
理解React中三种Diff算法的概念
使用JS中createElement的方式创建虚拟DOM
使用ReactDOM.render方法
使用JSX语法并理解其本质
掌握创建组件的两种方式
理解有状态组件和无状态组件的本质区别
理解props和state的区别

相关文章

  • 2018 年,React 将独占前端框架鳌头?
  • 前端框架三巨头年度走势对比:Vue 增长率最高
  • React数据流和组件间的沟通总结
  • 单向数据流和双向绑定各有什么优缺点?
  • 怎么更好的理解虚拟DOM?
  • React中文文档 - 版本较低
  • React 源码剖析系列 - 不可思议的 react diff
  • 深入浅出React(四):虚拟DOM Diff算法解析
  • 一看就懂的ReactJs入门教程(精华版)
  • CSS Modules 用法教程
  • 将MarkDown转换为HTML页面
  • win7命令行 端口占用 查询进程号 杀进程

你可能感兴趣的:(react)