React

作者: 田成力 | 来源:发表于2019-10-10 20:23 被阅读0次

    什么是React?

    React 是一个用于构建用户界面的框架(采用的是MVC模式):集中处理VIEW这一层
    核心专注于视图,目的实现组件化开发
    什么是组件化开发?
    我们可以很直观的将一个复杂的页面分割成若干个独立组件,每个组件包含自己的逻辑和样式 再将这些独立组件组合完成一个复杂的页面。 这样既减少了逻辑复杂度,又实现了代码的重用

    可组合
    可重用
    可维护
    安装react脚手架(跑环境)
    在全局下安装create-react-app
    打开命令行工具输入以下代码
    npm install create-react-app -g
    默认会自动安装React,react-dom两部分

    使用react脚手架创建项目

    需要在哪个文件夹下创建react项目就在那个项目下打开命令行工具输入以下代码
    create-react-app xxx(xxx为项目名称:不能出现大写字母)
    cd xxx(转到项目目录下)
    yarn start或npm start启动项目即可

    react和react-dom模块

    在js文件的顶部导入react和react-dom
    react:核心模块(生命周期等钩子函数都是在这里定义的)
    react-dom:提供与DOM相关的功能,会在window下增加ReactDOM属性,内部比较重要的方法是render,将react元素对象或者react组件插入到页面中

    import React from 'react';//核心基础 模块(生命周期)
    //用来把react元素(jsx元素)渲染成真实的DOM结构并添加到页面当中
    import ReactDOM,{render} from 'react-dom';
    //ReactDOM中就一个方法比较常用叫render
    //可以把render从react-dom中解构出来
    

    ES6中的模块导入和导出

    //=>A模块
    import Temp,{lib} from "./B";//=>把导入的Temp中的部分属性方法进行解构赋值
    new Temp().init(); //new Temp生成一个实例,并且执行它原型上的init方法
    lib();//=>Temp.lib()
    
    //=>B模块
    export default class Temp {
        init() {
            console.log(`hello world`);
        }
        static lib(){//=>Temp.lib
            
        }
    }
    

    react中的jsx语法与render渲染方法

    JSX语法

    react构建页面有自己的语法:JSX语法;

    jsx是一种JS和html混合的语法,HTML 语言直接写在 JavaScript 语言之中,不加任何引号,这就是 JSX 的语法,它允许 HTML 与 JavaScript 的混写;JSX最后会通过babel进行转义
    jsx中的html部分和原生html基本一样(也有不一样的比如class要用className,label的for要用htmlFor),并且属性名要使用驼峰命名法;
    用jsx语法写的内容叫做jsx元素(虚拟DOM元素);
    jsx元素具有不变性 :只在页面初次加载的时候渲染一次,以后只能通过更改状态和属性来重新渲染;

    jsx表达式的用法

    • 1、可以放JS的执行结果:在react中想将js当作变量引入到jsx中需要使用{}
    • 2.在jsx中,相邻的两个jsx元素 渲染时需要外面包裹一层元素(只能有一个根元素)
    function build(str) {
        return <h1>{str.name}</h1><h1>{str.name}</h1>;//在jsx语法中,这种写法是错误的
    }
    
    • 3、如果多个元素需要在return后面换行,我们需要加一个'()'当作一个整体返回
    • 4、可以把JSX元素当作函数的返回值
    • 5、用<{}来区分是html还是js表达式:遇到 HTML 标签(以< 开头),就用 HTML 规则解析;遇到代码块(以{开头),就用 JavaScript 规则解析
    • 6、循环绑定需要给元素设置唯一的KEY属性(属性值是唯一的)

    jsx属性的用法

    在jsx中分为普通属性和特殊属性

    • 1、普通属性:和html中的一样
    • 2、特殊的属性如class,要使用className;for(label中的for)要使用htmlFor
    • 3、style要采用对象的方式
    import React from 'react';
    import ReactDOM,{render} from 'react-dom';
    let str='<h1>纯标签</h1>';
    let style={background:'red'};
    render(<div>
    <li className="aa"></li>
    <li htmlFor="aa" style={style}></li>
    <li dangerouslySetInnerHTML={{__html:str}}></li>
    </div>,window.root);
    
    • 4、dangerouslyInnerHTML插入html(基本上用不到)

    React.createElement:

    React.createElement用来创建一个react对象,具有一个type属性代表当前的节点类型,还有节点的属性props;
    React.createElement("div",null,"姜,",React.createElement("span",null,"帅哥"))
    //结果如下

    {type:'div',props:{className:"red",children:['姜',{type:'span',props:{id:'handsome',children:'帅哥'}}]}}
    
    
    
    • render():此方法是由react-dom模块中提供的方法

    第一个参数为要渲染的jsx元素

    第二个参数为要把渲染的DOM结构插入到哪个容器当中

    第三个参数为渲染完成后需要执行的回调函数;

    用来将虚拟DOM元素(jsx元素)渲染成真实的DOM;

    ReactDOM.render(<div className='app'>姜<span id='handsome'>帅哥</span></div>,window.root); react中的虚拟DOM
    我们在react中创建的组件和用jsx语法写出的标签等,都是虚拟的DOM元素:
    组件并不是真实的 DOM 节点,而是存在于内存之中的一种数据结构,叫做虚拟 DOM (virtual DOM)。
    只有当它插入文档以后,才会变成真实的 DOM 。根据 React 的设计,所有的 DOM 变动,都先在虚拟 DOM 上发生,然后再将实际发生变动的部分,反映在真实 DOM上,这种算法叫做 DOM diff ,它可以极大提高网页的性能表现。

    虚拟DOM渲染过程(虚拟DOM—>真实DOM)

    • 1、构建虚拟DOM元素(jsx元素):
      在js中用jsx语法写的jsx元素就是虚拟DOM元素;(render中的元素)
    render(<h1 className='red'>心有猛虎,细嗅蔷薇</h1>,window.root)
    
    
    • 2、调用render方法把虚拟DOM元素渲染成真实的DOM结构
    • 1)render方法会先用babel把jsx元素转化为ES5语法
      'h1', {className: 'red'}, '心有猛虎,细嗅蔷薇'
    • 2)再用React.createElement方法根据上面的结果创建出一个react对象
      React.createElement('h1', {className: 'red'}, '心有猛虎,细嗅蔷薇')结果如下
      {type: "h1", props: {className: "red", children: "心有猛虎,细嗅蔷薇"}}
    • 3)此时render方法将react对象转化为真正的DOM结构,渲染到页面上
    render(<h1 className='red'>心有猛虎,细嗅蔷薇</h1>,window.root)
    自己模拟React.createElement方法与render方法:(render不完善)
    
    function createElement(type,props,...children) {
    if(children.length === 1) children = children[0];
    return {type,props:{...props,children:children}}
    }
    let myRender = (obj, container,callback) => {
    let {type, props} = obj;
    let ele = document.createElement(type);//创建第一层
    for (let key in props) {
    if (key === 'children') {
    //children可能是数组也可能是一个字符串(纯文本)
    if ({}.toString.call(props[key]).slice(8,-1)==='Array') {
    props[key].forEach(item => {
    if (typeof item === 'object') {//如果子元素是对象,那就继续调用render
    myRender(item, ele);
    } else {
    ele.appendChild(document.createTextNode(item));
    }
    })
    } else {//字符串的话直接创建文本节点并插入到元素中
    ele.appendChild(document.createTextNode(props[key]));
    }
    } else if (key === 'className') {//className要做单独处理
    ele.setAttribute('class', props[key]);
    } else {
    ele.setAttribute(key, props[key]);//其他的直接设置(style等属性未做处理)
    }
    }
    container.appendChild(ele);//把DOM元素插入到html中
    callback && callback();//=>添加成功后触发回调函数执行
    };
    

    react中的组件

    react元素是是组件组成的基本单位
    组件有两种声明方式

    1、第一种方式是函数声明
    每一个创建的组件都是一个自定义的标签,在标签上设置的属性都会转换为react对象的props属性,当react渲染DOM的时候会把props传递给下面例子中的Build函数

    import React from 'react';
    import ReactDOM,{render} from 'react-dom';
    let school1={name:'zfpx',age:10};
    let school2={name:'珠峰',age:8};
    let Build=function (props) {// "函数"(组件)的参数是属性
    return <p>{props&&props.name}{props&&props.age}</p>;
    };
    render(<div>
    <Build name={school1.name} age={school1.age}/>
    <Build {...school2}/>//利用解构赋值将对象中的内容解构出来传递给Build组件
    <h1></h1>
    </div>,window.root);
    
    

    2、第二种是以类的形式声明

    
    import React,{Component} from 'react';
    import ReactDOM from 'react-dom';
    let school1 = {name:'珠峰',age:8};
    let school2 = {name:'珠峰',age:0};
    //1.类声明的组件有this this上有props属性
    class Build extends Component{//Component这个基类上有this.setState
        render(){ // 这个组件在调用时默认会调用render方法(此render相当于注册组件)
            let {name,age} = this.props;
            return <p>{name} {age}</p>
            // return  <p>{this.props.name} {this.props.age}</p>
        }
    }
    ReactDOM.render(
    此render的渲染步骤
    1)先渲染外层div,将jsx元素转化为ES5的语法,然后调用React.createElement方法创建react对象,当遇到div下的子元素的时候,会进行以下操作
    //1、发现Build 是一个继承Component的类,它会创建这个类的一个实例,并且执行原型上的render方法
    //2、执行原型上的render返回JSX元素,然后继续执行外层的render
    2)继续将上面返回的jsx元素转化为es5语法,调用React.createElement方法创建虚拟DOM元素对象,Build 这个位置被返回的元素对象替换
    3)外层ReactDOM.render将上面生成的react对象渲染成真实的DOM结构,并追加到页面中;
    <div>
        <Build name={school1.name} age={school1.age}/>
        <Build {...school2} />
    </div>,window.root);
    

    区别:类声明有状态,this,和生命周期
    声明组件的规则:

    1、首字母必须大写,目的是为了和JSX元素进行区分
    2、组件定义后可以像JSX元素一样进行使用
    3、可以通过render方法将组件渲染成真实DOM(做了优化 只会重新渲染改变的地方)
    单闭合标签不能添加子节点;双闭合标签可以

    组件中属性(props)和状态(state)的区别

    属性和状态的变化都会影响视图更新,但是两者导致视图刷新的机制是不一样的;

    1、确切的说不是因为props更新了导致的视图刷新,而是传递属性的组件(父组件)重新的渲染了,从而导致接受属性的组件(子组件)重新渲染,这样属性就也随着更新了;
    2、状态导致的视图刷新是只要通过this.setState改变状态,视图就会刷新;
    组件的数据来源有两个地方:

    1、通过props(属性) 获取数据:
    外界传递进来的(存在默认属性和属性校验),是只读的,不能修改;
    有属性校验模块:prop-types(用来校验属性的类型和是否必须设置某属性)
    用yarn add prop-types来安装。用来校验属性的

    import React,{Component} from 'react';
    import ReactDom,{render} from 'react-dom';
    import PropTypes from 'prop-types';
    // 1、默认的属性必须写在defaultProps中(不能改名字)
    // 2、校验器的名字必须是propTypes(不能改名字);
    class School extends Component{//类上的属性(把类当作对象添加的属性)就叫做静态属性
    static defaultProps={//会先默认调用defaultProps
    name:'珠峰',
    age:1
    };
    static propTypes={//校验属性的值(number)类型和是否必须设置该属性(isRequired)
    age:PropTypes.number.isRequired
    };
    constructor(props){
    //如果想在constructor中使用props,则只能传递参数props,在constructor外面可以直接用this.props
    //不能在组件中更改属性
    super();
    console.log(this.props)//undefined
    }
    render(){//渲染组件的,返回一个组件
    //如果没有默认的defaultProps,也没有传递props,则this.props是一个空对象
    return <h1>{this.props.name} {this.props.age}</h1>
    }
    }
    //name={'珠峰培训'}:这样写大括号中就可以是变量
    render(<School name={'珠峰培训'} age={9}/>,window.root);
    
    

    2、this.state(状态) 状态是自己的:

    是可读写的(写了状态就必须给默认值);
    我们可以通过this.setState({})方法来修改状态,由于修改状态会引起视图刷新(即更新DOM),而有关DOM的操作都是异步的,所以this.setState({})是异步的;

    import React,{Component} from 'react';
    import ReactDom,{render} from 'react-dom';
    //1、state变化可以更新视图,只能通过this.setState({})来更改状态,调用后会更新视图
    class Counter extends Component{
    constructor(){
    super();
    console.log(this.state);//默认为undefined
    this.state={count:0}
    }
    add=(val)=>{ //val后面省略了一个参数e(事件对象),是onClick事件默认传进来的,放在val的后面,因为bind的传参机制
    //setState方法会默认按最后的设置进行执行
    // this.setState({count:this.state.count+val});
    this.setState({count:this.state.count+val});//会执行这个
    //setState有两种写法,一种是传入对象的方式,一种是传入函数的方式
    //下一个状态是依赖于上一个状态时,需要写成函数的方式(回调函数)
    this.setState(prevState=>({count:prevState.count+val}));//如果返回的就是一个对象,则需要用'()'包裹.如果不用小括号包裹,则需要加return。因为函数如果不加return或者return后什么也不写,函数的返回值则为undefined
    }); //等同于下面这种写法
    //this.setState({count:this.state.count+val},function () {
    // this.setState({count:this.state.count+val});
    //};
    render(){
    return <p>
    {this.state.count}
    /因为add方法是在类里面声明的,属于ES7语法,所以 add方法中的this是指向当前实例的,所以bind不需要改变this指向/
    <button onClick={this.add.bind(null,2)}>+</button>
    </p>
    }
    }
    render(<Counter/>,window.root);
    

    复合组件与数据传递
    复合组件:将多个组件进行组合嵌套;
    复合组件之间的数据传递:

    1、<font color=red>父传子</font>:父组件传递给子组件数据通过设置属性的方式

    
    import React,{Component} from 'react';
    import ReactDom,{render} from 'react-dom';
    import PropTypes from 'prop-types';
    import 'bootstrap/dist/css/bootstrap.css';
    //1、什么是复合组件,将多个组件进行组合
    //2、结构非常复杂时可以把组件分离开
    //3、复合组件 有父子关系,父的数据传递给子(通过给子组件设置属性)
    // Panel组件 Heading Body
    class Counter extends Component{
    render(){
    //由于在Counter中传递了属性,所以这里可以用this.props获取到;
    let {header:header,body:body}=this.props;
    return (
    <div className='container'>
    <div className='panel-default panel'>
    //这里写了之后就可以在下面的子组件中拿到对应的props值
    <Header header={header}></Header>
    <Body body={body}></Body>
    </div>
    </div>
    )
    }
    }
    //react中需要把属性一层一层向下传递:单向数据流;
    class Header extends Component{
    render(){
    return (
    <div className='panel-heading'>
    //上面传递了之后就可以直接在此处使用this.props属性了;
    {this.props.header};
    </div>
    )
    }
    }
    class Body extends Component{
    render(){
    return (
    <div className={'panel-body'}>
    {this.props.body}
    </div>
    )
    }
    }
    let data={header:'我很帅',body:'长的英俊'};
    render(<Counter {...data}/>,window.root);
    

    2、<font color=red>子传父</font>:通过父亲传递给儿子一个函数,儿子调用父亲的函数将要改的值传递给父亲,父亲更新值,刷新视图;

    import React, {Component} from 'react';
    import ReactDom, {render} from 'react-dom';
    import 'bootstrap/dist/css/bootstrap.css';
    //1、子传父 通过父亲传递给儿子一个函数,儿子调用父亲的函数将要修改的值传递给父亲,父亲更新值,刷新视图;
    class Counter extends Component {
    constructor() {
    super();
    this.state = {color: 'primary'};
    }
    changeColor = (color) => {
    this.setState({color});
    };
    render() {
    let {header} = this.props;
    return (
    <div className='container'>
    <div className={'panel panel-' + this.state.color}>
    <Header header={header} color={this.changeColor}></Header>
    </div>
    </div>
    )
    }
    }
    //react中需要把属性一层一层向下传递:单向数据流;
    class Header extends Component {
    clickColor = () => {
    this.props.color('danger');//儿子调用父亲的方法,把要改的值传递给父亲,让父亲自己修改
    };
    render() {
    return (
    <div className='panel-heading'>
    {this.props.header}
    <button className={btn btn-danger} onClick={this.clickColor}>改颜色</button>
    </div>
    )
    }
    }
    let data = {header: '我很帅'};
    render(<Counter {...data}/>, window.root);
    

    ref属性

    ref可以写在组件上也可以写在DOM元素上,获取的时候得到两者的结果是不同的;

    写在组件上时(这里的组件是有状态的组件),通过refs获取的是组件的实例;
    写在DOM元素上时,通过refs获取的是具体的DOM元素节点.(获取的是真实DOM)
    ref的值可写为两种形式

    1、直接写为一个值
    通过ref设置的属性:可以通过this.refs.xxx获取到对应ref值为xxx的dom元素或者组件的实例

    2、写成一个函数的形式
    写成函数时,这个函数将会在渲染成真实DOM结构或组件被挂载后执行,函数的参数为真实的DOM结构或组件的实例

    
    
    //如上面非受控组件中的例子
    //1、直接将ref写为一个值
    <input type="text" ref='a'/>
    //2、将ref写为一个函数
    <input type="text" ref={(x)=>this. c=x}/>
    {/* x 代表真实的DOM(写成函数的形式会默认传入当前设置ref的DOM结构 ),上面这种写法相当于把DOM结构挂载到了this的自定义属性c上,以后可以直接用this.c拿到这个input的DOM结构*/}
    resultChange=()=>{//通过ref设置的属性:可以通过this.refs.xxx获取到对应ref值为xxx的dom元素
    this.c.value=parseFloat(this.refs.a.value)+parseFloat(this.refs.b.value);
    };
    

    受控组件与非受控组件(受状态控制)

    组件是否与this.state有关系区分为受控与非受控组件,受控组件可以赋予默认的值

    受控组件

    需要与onInput/onChange/disabed/readOnly等控制value/checked的属性或事件一起使用

    import React, {Component} from 'react';
    import ReactDom, {render} from 'react-dom';
    class Input extends Component {
    //1、受状态控制的组件,必须要有onChange方法,否则不能使用;
    //2、受控组件可以赋予默认值(官方推荐使用受控组件)
    constructor() {
    super();
    this.state = {val: '', a: 1, b: 2};//初始化状态:设置默认值
    }
    inputChange = (val, e) => {//处理多个输入框的值映射到this.state的方法
    //val:要修改的state中的属性的名称
    //e:事件源(e.target.value是要修改成的值)
    this.setState({[val]: parseFloat(e.target.value)||0});//更改状态
    };
    render() {
    return (
    <div>
    //下面两个input都受this.state影响(受控组件)
    <input type="text" value={this.state.a} onChange={this.inputChange.bind(null, 'a')}/>
    <input type="text" value={this.state.b} onChange={e =>{
    //方法执行的时候要用到事件对象e,所以必须要传
    this.inputChange('b', e)
    }}/>
    {this.state.a + this.state.b}
    </div>
    )
    }
    }
    render(<Input/>, window.root);
    

    非受控组件(基于ref来管理)

    import React,{Component} from 'react';
    import ReactDom,{render} from 'react-dom';
    class Input extends Component{
    //输入框value值不受状态控制,不能初始化默认值
    resultChange=()=>{//通过ref设置的属性:可以通过this.refs.xxx获取到对应ref值为xxx的dom元素
    this.c.value=parseFloat(this.refs.a.value)+parseFloat(this.refs.b.value);
    };
    render(){
    return (
    <div onChange={this.resultChange}>
    <input type="text" ref='a'/>
    <input type="text" ref='b'/>
    {/x代表真实的DOM(写成函数的形式会默认传入当前设置ref的DOM结构),下面这种写法相当于把元素挂载到了this的自定义属性c上/}
    <input type="text" ref={(x)=>this.c=x}/>
    </div>
    )
    }
    }
    render(<Input/>,document.getElementById('root'));
    

    react元素中的事件绑定

    给react元素(jsx元素)事件绑定对应的方法时,方法如果不做处理,那么执行的时候方法中的this默认是undefined
    可通过如下方式解决:

    1、使用bind方式;(绑定的时候处理)

    2、使用ES6的箭头函数(绑定的时候处理:在外层套一层箭头函数)

    3、使用ES7的箭头函数(在类中定义的时候处理:直接写为xxx=()=>{} )

    注:
    Class中声明的方法this默认不绑定到实例上,需要手动绑定.ES6语法规范;
    在Class中利用箭头函数声明的方法,被规定为ES7语法,方法中的this都是当前实例。

    React中的生命周期

    在react操作过程中,设定了很多的执行阶段,每个阶段提供了不同方法,

    [初期渲染]
    defaultProps:
    默认属性设置与校验
    constructor:
    获取属性和设置默认初始状态
    componentWillMount:(只执行一次)
    constructor执行完成后,DOM结构开始加载之前,执行此方法,
    render(开始渲染)
    componentDidMount(只执行一次)
    DOM结构加载完成后会自动触发此函数
    [状态发生改变时触发]
    shouldComponentUpdate
    状态更新时会触发此方法,返回布尔值
    componentWillUpdate
    如果shouldComponentUpdate返回true则执行此函数,否则不执行
    render(开始渲染)
    componentDidUpdate
    组件完成更新后触发此方法
    [卸载组件时触发]

    ReactDOM.unmountComponentAtNode
    删除某个组件
    componentWillUnmount(组件将要卸载)
    组件移除时会调用此方法,一般在这个方法中我们要清除定时器
    [属性发生改变时触发]
    componentWillReceiveProps

    
    import React, {Component, PureComponent} from 'react';
    import ReactDom, {render} from 'react-dom';
    class Counter extends Component {/PureComponent是纯组件:比较的是状态的地址,如果是同一个地址,则不会更新,所以状态最好采用新的状态替换掉老的/
    static defaultProps = {
    name: 'zfpx'
    };
    constructor(props) {
    super();
    this.state = {number: 0};
    console.log('1、constructor');
    }
    componentWillMount() {//同步代码放在此处最好:如获取本地的数据:在渲染之前获取数据,只渲染一次
    console.log('2、父组件将要加载');
    }
    componentDidMount() {
    this.setState({number: this.state.number + 1});
    console.log('5、父组件已经挂载完成');
    }
    click = (e) => {
    this.setState({number: this.state.number + 1});
    };
    //react可以在shouldComponentUpdate方法中优化 PureComponent 可以帮我们做这件事
    shouldComponentUpdate(nextProps, nextState) {//分别代表下一次的属性和下一次的状态
    //组件是否需要更新
    console.log('父组件是否需要更新');
    return nextState.number % 2;//如果此函数返回了false,就不会调用render方法了
    }
    //乱用this.setState({})会造成栈溢出
    componentWillUpdate() {
    console.log('父组件将要更新');
    }
    componentDidUpdate() {
    console.log('父组件完成更新');
    }
    render() {
    console.log(3、父组件render(渲染));
    return (
    <div>
    <p>{this.state.number}</p>
    <ChildCounter n={this.state.number}/>
    <button onClick={this.click}>+</button>
    </div>
    )
    }
    }
    class ChildCounter extends Component {
    componentWillReceiveProps(newProps) {//第一次不会执行,之后属性更新时才会执行
    /父组件渲染完成后发现有状态更新,执行componentWillReceiveProps,接着执行shouldComponentUpdate,然后子组件渲染/
    /componentWillReceiveProps只写在子组件中/
    console.log('子有新的属性');
    }
    render() {
    console.log('4、child render');
    return (
    <div>
    {this.props.n}
    </div>
    )
    }
    shouldComponentUpdate(nextProps, nextState) {
    console.log('子组件是否需要更新');
    return nextProps.n % 3;
    }
    }
    render(<Counter name={'计数器'}/>, window.root);
    

    <font color=red>注意</font>:

    最好不要在任何一个状态更新时触发的方法中写this.setState(),如果不加条件控制的话,会再次触发状态更新而导致方法执行,这样就会造成栈溢出;

    /页面初始化时触发/
    //defaultProps:
    //constructor
    //componentWillMount(将要挂载)页面初次加载时执行1次
    //render
    //componentDidMount(完成挂载)页面初次加载完成时执行1次
    /状态更新时会触发/
    //shouldComponentUpdate
    //componentWillUpdate
    //render
    //componentDidUpdat
    /属性更新/
    //componentWillReceiveProps
    /卸载/
    // componentWillUnmount
    
    获取组件的所有子节点
    在函数式声明的组件中我们用props.children来获取组件的所有子节点;
    在以类声明的组件中我们用this.props.children来获取组件的所有子节点;
    
    render(<Dinner>)
    

    相关文章

      网友评论

          本文标题:React

          本文链接:https://www.haomeiwen.com/subject/zbaapctx.html