zoukankan      html  css  js  c++  java
  • 第五课之初步认识react

    课程回顾

    开发环境和生产环境

    开发环境和生产环境有很多通用的代码,可以将通用的代码提取出来,例如loaders,output等,所以可以形成三个文件,webpack.prod.config.js、webpack.dev.config.js、webpack.common.config.js,分离出来如何将他们分别合并,这时候就需要使用到webpack-merge,进行配置合并

    课程内容

    • 1.课程回顾
    • 2.react介绍
    • 3.react安装
    • 4.生命周期、事件、数据流讲解
    • 5.父子组件传值

    1.什么是React

    React是Facebook推出的一个JavaScript库,它的口号就是“用来创建用户界面的JavaScript库”,所以它只是和用户界面打交道,可以把它看成MVC中的V(视图)层。

    为什么使用react

    • 虚拟dom效率性能高,不需要操作dom(频繁操作dom很消耗性能)
    • 使用组件化开发方式,高复用、维护容易,逻辑清晰
    • 技术成熟,社区完善,插件齐全,适用于大型Web项目(生态系统健全),由Facebook专门的团队维护,技术支持可靠,是现在最主流的技术之一
    • 全局单向数据流,数据流向清晰,大型项目不在话下

    组件化

    • 每一个ReactJS文件都是一个组件,含视图、逻辑操作、数据
    • 复用、封装性强

    单向数据流

    • 子组件对于父组件传递过来的数据是【只读】的,不能直接更新父组件数据、这样组件更加简单易把握、只会在父组件修改数据,追查问题的时候可以跟快捷
    • 顶层组件某个props改变会递归遍历整棵组件树,重新渲染使用这个属性的插件

    虚拟dom

    • React的设计中,开发者基本上无需操纵实际的DOM节点,每个React组件都是用Virtual DOM渲染的。
    • 用 JavaScript 对象结构表示 DOM 树的结构,然后用这个树构建一个真正的 DOM 树
    • 新的虚拟 DOM 与原来的虚拟 DOM进行比对时,它会进行同层比较,即相同的节点层进行比较,如果不同则直接将原始虚拟 DOM 中该节点层及以下的节点全部删除,重新生成新的虚拟 DOM 节点,而不会继续向下比对
    jsx
    1. JSX 是 JavaScript 语言的一种语法扩展,长得像 HTML,但并不是 HTML。
    2. React.js 可以用 JSX 来描述你的组件长什么样的。
    3. JSX 在编译的时候会变成相应的 JavaScript 对象描述。
    4. react-dom 负责把这个用来描述 UI 信息的 JavaScript 对象变成 DOM 元素,并且渲染到页面上。
    // 第一步:jsx代码
    <button className="btn btn-blue">
        <em>Confirm</em>
    </button>
    // 第二步解析成对象
    {
      type: 'button',
      props: {
        className: 'btn btn-blue',
        children: {
          type: 'em',
          props: {
            children: 'Confirm'
          }
        }
      }
    }
    // 第三步:根据对象创建标签
    // 参数1:元素名称  
    // 参数2:元素属性对象(null表示无)  
    // 参数3:当前元素的子元素string||createElement() 的返回值
    
    React.createElement("button", {
          className: "btn btn-blue"
        },React.createElement("em", null, "Confirm"))
      
    // 第四步:渲染
    ReactDOM.render(dom, document.getElementById('app'))
    
    key
    • 例如:在遍历数据时,推荐在组件中使用 key 属性

    • 循环需要加key:你在 JSX 模板中遍历 state 中某个数据时,为什么不加 key 值浏览器会报警告,这是因为你不再遍历的每条数据加上 key 值,更改 state 中那条数据的值,生成虚拟 DOM 后,React 就不知道原始遍历的数据和这次更新后遍历的数据一一对应的关系,就会再次重新渲染,而加上 key 值,它则能迅速比对出有差异的部分进行部分的更新。

    • 为什么不建议用 index 作为 key 值:因为当你插入、 删除中间的数据时,从改变的那个数据开始,后续每个数据的 index 值就会变,从而就导致了每个数据的 key 值相应变化了,这样依旧会引起大规模渲染,这就是其中的原因

    • 流程图

    虚拟dom、jsx

    • 从 JSX 到页面到底经过了什么样的过程:

    2.react安装

    // 第一步:安装react react-dom,以及jsx编译
    npm i -D react react-dom @babel/preset-react
    // 第二步:.babelrc添加
    "presets": [
      "@babel/env",
      "@babel/react"
    ],
    
    // 第三步:添加react.js
    // 1. 导入 react
    import React, { Component } from 'react'
    import ReactDOM from 'react-dom'
    // 由于react实现机制,组件名称必须要大写
    class MyComponet extends Component {
       constructor(props) {
            super(props);
            this.state = {};
        }
        render() {
            return (
                // 此处注释的写法 
                <button className="btn btn-blue">
                    <em>Confirm</em>
                </button>
            )
        }
    }
    // 第四步:渲染
    // 参数1:虚拟dom对象  参数2:dom对象表示渲染到哪个元素内 参数3:回调函数
    ReactDOM.render(<MyComponet />, document.getElementById('root'))
    
    // 第五步:修改入口文件
    entry: './src/js/react.js',  //指定打包的入口文件
    
    
    • 组件名称命名用大驼峰
    • 如果在 JSX 中给元素添加类, 需要使用 className 代替 class
    • 在 JSX 中可以直接使用 JS代码,直接在 JSX 中通过 {} 中间写 JS代码即可

    生命周期

    1. 挂载卸载过程
    • constructor():React数据的初始化,它接受两个参数:props和context,当想在函数内部使用这两个参数时,需使用super()传入这两个参数。
    • componentWillMount():它代表的过程是组件已经经历了constructor()初始化数据后,但是还未渲染DOM时
    • componentDidMount():组件第一次渲染完成,此时dom节点已经生成,可以在这里调用ajax请求,返回数据setState后组件会重新渲染
    • componentWillUnmount ():在此处完成组件的卸载和数据的销毁。(移除计时器)
    1. 更新过程
    • componentWillReceiveProps (nextProps):接受一个参数nextProps,通过对比nextProps和this.props是否改变,判断要做的操作,只要父组件改变都会造成子组件componentWillReceiveProps接收,也会增加组件的重绘次数,浪费性能,写法不好容易死循环
    • shouldComponentUpdate(nextProps,nextState):主要用于性能优化(阻止组件渲染),因为react父组件的重新渲染会导致其所有子组件的重新渲染,这个时候其实我们是不需要所有子组件都跟着重新渲染的,因此需要在子组件的该生命周期中做判断
    • componentWillUpdate (nextProps,nextState):shouldComponentUpdate返回true以后,组件进入重新渲染的流程,进入componentWillUpdate,这里同样可以拿到nextProps和nextState。
    • componentDidUpdate(prevProps,prevState):react只会在第一次初始化成功会进入componentDidmount,之后每次重新渲染后都会进入这个生命周期,这里可以拿到prevProps和prevState,即更新前的props和state
    • render():render函数会插入jsx生成的dom结构,react会生成一份虚拟dom树,在每一次组件更新时,在此react会通过其diff算法比较更新前后的新旧DOM树,比较以后,找到最小的有差异的DOM节点,并重新渲染
    1. React新增的生命周期
    • getDerivedStateFromProps(nextProps, prevState):代替componentWillReceiveProps(),获取props并修改state
    • getSnapshotBeforeUpdate(prevProps, prevState):一般的用法就是获取更新前的DOM
    1. React17版本废弃的生命周期
    • componentWillMount
    • componentWillReceiveProps(可以用getDerivedStateFromProps和componentDidUpdate搭配使用替代)
    • componentWillUpdate

    修改后的生命周期

    // 之前
    componentWillReceiveProps(nextProps) {
      if (nextProps.isLogin !== this.props.isLogin) {
        this.setState({ 
          isLogin: nextProps.isLogin,   
        });
        this.handleClose();
      }
    }
    // 现在
    static getDerivedStateFromProps(nextProps, prevState) {
      if (nextProps.isLogin !== prevState.isLogin) {
        return {
          isLogin: nextProps.isLogin,
        };
      }
      return null;
    }
    
    componentDidUpdate(prevProps, prevState) {
      if (prevState.isLogin !== this.state.isLogin) {
        this.handleClose();
      }
    }
    
    

    使用方式

    class MyComponet extends Component {
       constructor(props) {
            super(props);
            this.state = {};
        }
        // 日常使用的生命周期
        // 新增的生命周期
        static getDerivedStateFromProps(nextProps, prevState) {
            console.log('判断前后两个 props     是否相同------getDerivedStateFromProps')
            return null;
        }
        componentDidUpdate() {
            console.log('数据已经更新---------componentDidUpdate')
        }
        
        // 组件第一次渲染完成,此时dom节点已经生成,可以在这里调用ajax请求,返回数据setState后组件会重新渲染
        componentDidMount() {
            console.log('组件加载后--------componentDidMount')
        }
        // 即将废弃
        // componentWillReceiveProps(nextProps) {
        //     console.log('接收props--------componentWillReceiveProps')
        // }
        // 性能优化
        // 你可以通过这个方法控制组件是否重新渲染。如果返回 false     组件就不会重新渲染。
        shouldComponentUpdate(nextProps, nextState){
             // 值没有发生过变化,不需要重新触发render
             if(nextState.Number == this.state.Number){
                return false
             }
        }
        render() {
            return (
                // 此处注释的写法 
                <button className="btn btn-blue">
                    <em>Confirm</em>
                </button>
            )
        }
    }
    
    

    render(不要在render里setState)

    1. 一个组件类必须要实现一个 render 方法。
    2. 这个 render 方法必须要返回一个 JSX 元素。
    3. 必须要用一个外层的 JSX 元素把所有内容包裹起来。
    // 错误
    render () {
      return (
        <div>第一个</div>
        <div>第二个</div>
      )
    }
    
    // 正确
    render () {
      return (
        <div>
          <div>第一个</div>
          <div>第二个</div>
        </div>
      )
    }
    

    react事件

    // 绑定事件,this指向会改变所以我们通过代码控制不改变this的指向
    // 写法一:构造函数里绑定事件,效率高
    constructor(props) {
        super(props);
        this.state = {};
        this.showInfo = this.showInfo.bind(this);
    }
    showInfo() {
        console.log(2312321)
    }
    <button className="btn btn-blue" onClick={this.showInfo}>
        <em>Confirm</em>
    </button>
    
    // 写法二:使用bind this
    showInfo() {
        console.log(2312321)
    }
    <button className="btn btn-blue" onClick={this.showInfo.bind(this)}>
        <em>Confirm</em>
    </button>
    
    // 写法三:方法使用箭头函数,this指向不会改变,简单易用,效率高,建议使用
    showInfo=()=> {
        console.log(2312321)
    }
    <button className="btn btn-blue" onClick={this.showInfo}>
        <em>Confirm</em>
    </button>
    
    // 写法四:在标签上调用函数,每次渲染会调用,不建议使用
    <button className="btn btn-blue" onClick={()=>this.showInfo()}>
        <em>Confirm</em>
    </button>
    

    数据讲解

    1.state:内部定义,它只是用来控制这个组件本身自己的状态,页面渲染通过setState进行完成。

    2.setState:

    • 当我们调用这个函数的时候,React.js 会更新组件的状态 state ,并且重新调用 render 方法,然后再把 render 方法所渲染的最新的内容显示到页面上
    • React.js为了批次与效能并不会马上修改state。而是把这个对象放到一个更新队列里面,稍后才会从队列当中把新的状态提取出来合并到 state 当中,然后再触发组件更新。
    // 想要即时获得改变后的state
    // 方案一:使用回调函数
    this.setState({ val: this.state.val + 1 }, () => {
      console.log(this.state.val);     
    })
    
    // 方案二:setTimeout是异步方法,react无法直到开发者异步方法中想要渲染的顺序,所以在异步方法中调用setTimeout,react会即时渲染,不会使用批量更新
    setTimeout(_ => {
        this.setState({
            num: ++num
        })
        console.log(this.state.num);
    }, 0)
    

    3.props:外部传入,包括父子组件之间的通信,全局数据流的传递

    • 父子组件之间的通信,子组件无法直接修改父组件的props,通过this.props接收
    • 全局数据流的传递(dva)

    image

    父子组件传值

    • 父组件使用子组件并添加属性进行传值
    • 子组件接收属性或者方法
    • 实现父组件state的使用,以及子组件props的使用
    案例
    // 第一步:添加一个子组件ChildCom.js,组件首字母大写
    import React, { Component } from 'react'
    
    export default class ChildCom extends Component {
        constructor(props) {
            super(props);
            this.state = {
            };
        }
        clickBtn = () => {
            // 使用父组件的方法
            this.props.addChildNum();
        }
        render() {
            // 使用父组件的值
            const { childNum } = this.props;
            return (
                <div>
                    <div>子组件值:{childNum}</div>
                    <button onClick={this.clickBtn}>子组件点击调用父组件方法</button>
                </div>
            )
        }
    }
    
    // 第二步父组件添加
    // 1. 导入 react
    import React, { Component } from 'react'
    import ReactDOM from 'react-dom'
    // 导入子组件
    import ChildCom from './ChildCom';
    // 2. 创建 虚拟DOM
    // 参数1:元素名称  参数2:元素属性对象(null表示无)  参数3:当前元素的子元素string||createElement() 的返回值
    class MyComponet extends Component {
        constructor(props) {
            super(props);
            this.state = {
                num: 1,
                childNum: 1,
            };
        }
        ...
        addNum = () => {
            let { num } = this.state;
            this.setState({
                num: ++num
            })
        }
        addChildNum = () => {
            let { childNum } = this.state;
            this.setState({
                childNum: ++childNum
            })
        }
        render() {
            const { num, childNum } = this.state;
            return (
                <div>
                    <div>父组件值:{num}</div>
                    <button className="btn btn-blue" onClick={this.addNum}>
                        <em>Confirm</em>
                    </button>
                    <ChildCom childNum={childNum} addChildNum={this.addChildNum} />
                </div>
    
            )
        }
    }
    
    

    ref

    不借助插件的情况下,react中如何获取获取dom节点

    方式一

    使用原生js,需要在componentDidMount(组件渲染完成后获取)

    // 原生js获取获取dom节点
    document.getElementById('');
    ~
    ~
    ~
    
    方式二

    react提供的一个特殊的属性ref,可以绑定到render()输出的任何组件上,这个特殊的属性允许你引用 render() 返回的相应的dom节点

    // 第一步:添加ref属性
    <input value={num} ref="myIpt" readOnly />
    
    // 第二步:获取节点
    getDom = () => {
        var dom = this.refs.myIpt;
        dom.style.background='red';
        dom.focus();
    }
    

    父组件如何获取子组件的数据(props,state,event)

    // 第一步:父组件使用并子组件添加onRef={(child) => this.child = child}(可以取别的名字)
    // 作用是设置当前父组件的this.child等于返回的对象
     <ChildCom childNum={childNum} addChildNum={this.addChildNum} onRef={(child) => this.child = child} />
    
    // 第二步:子组件ChildCom添加
    componentDidMount() {
        // 将child传递给this.props.onRef()方法
        this.props.onRef && this.props.onRef(this);
    }
    
    // 第三步父组件使用
    console.log(this.child); // ChildCom {props: {…}, context: {…}, refs: {…}, updater: {…}, clickBtn: ƒ, …}
    
    • 百分之99的情况在不使用ref的情况下,react都能进行完成,频繁的dom操作是比较消耗性能的,所以在任何不需要ref去操作页面数据的的情况下,都不要使用ref去操作dom
  • 相关阅读:
    第一章 新手入门
    Excle生成T层加工ODS层存储过程
    Excel生成建表角本
    数据仓库建模技巧
    算法第四章作业
    删数问题
    算法第三章作业
    第三章上机实践报告
    第二章作业
    Thinkphp6框架学习:($this->error()undefined)Call to undefined method appindexcontrollerAdmin::error()
  • 原文地址:https://www.cnblogs.com/Hsong/p/13977174.html
Copyright © 2011-2022 走看看