React技术栈系列—基础01

React

React简介

于2013年来自Facebook开源项目。

和Angular不一样的是,React并不是一个完整的MVC/MVVM框架,它只专注于提供清晰、直接的View视图层解决方案。它的功能全部以构建组件视图为核心,并提供类似控制器的函数接口和生命周期函数。所以在React中么有控制器、没有服务、没有指令、没有过滤器等等这些。

Virtual DOM

是React中的一个非常重要的概念,在日常开发中,前端需要将后端的数据呈现到界面中,同事还要能对用户的操作提供反馈,并且作用到UI上,这些操作都离不开操作DOM,但是频繁的DOM操作会造成极大的资源浪费和引起性能瓶颈。于是React引入了Virtural DOM。核心就是在内存上构建虚拟DOM,然后计算改变前后的DOM区别,最后用最少的DOM操作对DOM进行操作再用虚拟DOM替换真是DOM。

DIFF算法

Virtual DOM技术使用了DIFF算法,DIFF算法是一个比较计算层次结构区别的算法,主要是用来计算DOM之间的差异。(说实话,我并不懂DIFF算法,只是知道Virtual DOM使用DIFF算法实现的。)也许上图会更直观一点:

JSX语法糖

React中使用JSX语法糖,JSX = JavaScript + XML。可以让开发者在JS文件中写HTML模板,代码语境不需要来回切换。比如:
jsx不能直接运行,是需要babel-loader中的react这个preset编译。此处不赘述,在React技术栈从0搭建中介绍。
需要注意的是:

import React, {Component} from 'react';
import {connect} from "react-redux";
import {getUserDataAction} from "../../Store/actionCreators";
import md5 from "md5"; 
const P_KEY = 'dmx.top';
class Login extends Component {

    constructor(props) {
        super(props);
        this.state = {
            "user_name": "",
            "user_pwd": ""
        }
    }

    render() {

        return (
            <div>
                <div className="login">
                    <div className="login-wrap">
                        <div className="avatar">
                            <img src="./uploads/logo.jpg" className="img-circle" alt="" />
                        </div>
                        <div className="col-md-offset-1 col-md-10">
                            <div className="input-group input-group-lg">
                                <span className="input-group-addon">
                                    <i className="fa fa-id-card-o"></i>
                                </span>
                                <input
                                    name="user_name"
                                    type="text"
                                    className="form-control"
                                    placeholder="请输入用户名"
                                    onChange={(e) => this._onInputChange(e)}
                                    onKeyUp={(e) => this._onInputKey(e)}
                                />
                            </div>
                            <div className="input-group input-group-lg">
                                <span className="input-group-addon">
                                    <i className="fa fa-key"></i>
                                </span>
                                <input
                                    name="user_pwd"
                                    type="password"
                                    className="form-control"
                                    placeholder="请输入密码"
                                    onChange={(e) => this._onInputChange(e)}
                                    onKeyUp={(e) => this._onInputKey(e)}
                                />
                            </div>
                            <button
                                type="submit"
                                className="btn btn-lg btn-danger btn-block"
                                onClick={(e) => this._onSubmit(e)}
                            >登 录
                            </button>
                        </div>
                    </div>
                </div>
            </div>
        );
    }

    //输入框改变

1) 必须被一个单独的大标签包裹,比如div。
2)单标签必须封闭,比如img , input 等
3)class要写成className, for要写成htmlFor
4)HTML注释不能使用,只能使用js的注释方法
5)原生标签比如p,li,div如果需要自定义属性,必须加data-前缀。自定义组件不需要。
6)js表达式用{}胆大括号包裹,在jsx中不能使用if else语句,但是可以使用conditinal(三元运算符)来替代。
7)可以运行函数:比如

    //输入框改变
    _onInputChange (e) {
        let inputValue = e.target.value,
            inputName  = e.target.name;
        this.setState({[inputName] : inputValue});
    }

8)如果要在React组件中写行内样式,需要还用双大括号包裹。比如:

<img src={icon_url.includes(undefined) ? icon : icon_url } style={{border: "1px solid #e0e0e0"}} />

9)可以使用数组,如果是jsx语法糖数组会被自动展开,但是,React会提示需要给展开的每一项加一个独特的key值,因为底层DIFF比对是根据key值来比对的。比如:

let arr = [1,2,3,4,5,6,7,8,9,10];
return (
         <div>
            <ul>
               {arr}
            </ul>
         </div>
 );
 

React中数据的传递


React中数据传递三兄弟:state/props/context

三基友之一:state

React把组件看成一个状态机(State Machines)。通过与用户交互,实现不同状态,然后渲染UI,让用户界面和数据保持一致。
在React中,只有更新三兄弟中的任何一个,才会引发Virtual DOM的改变,才能重新渲染用户界面。

import React from "react";
class App extends React.Component{
    constructor(){
        super();

        this.state = {
            a : 100,
            b : 200,
            c : 300
        }
    }

    add(){
            this.setState({a : this.state.a + 1});
    }

    render(){
        return (
            <div>
                <h1>我是APP组件</h1>
                <p>我有状态state</p>
                <p>a : {this.state.a}</p>
                <p>b : {this.state.b}</p>
                <p>c : {this.state.c}</p>
                <p>
                    <input type="button" value="按我" onClick={(this.add).bind(this)}/>
                </p>
            </div>
        )
    }
}

//向外暴露
export default App;

所以,
定义state: 在构造函数(即constructor)中使用this.state属性即可。
使用state: 在JSX中{this.state.a}
改变state: this.setState({a: this.state.a + 1}); 不能写成a: this.state.a++,因为state的属性只读,必须通过setState来改变,才能确保state在页面中安全。
state的是内部的,也被称为local state, 只有组件自己才能改变自己的state,别人想改变自己的state都不可能。

三基友之二:props

就是定义在自定义组件标签上面的值,就是props。当props改变的时候,会引发Virtual DOM的改变,从而引发视图的重绘。react崇尚数据的单向流动,所以设计的时候就是让数据从父组件流向子组件。props在子组件中是只读的,不能修改的。
如果父组件想和子组件通信,就需要使用自身属性往下传递需要通信的数据。

父组件:

import React from "react";
import MyCompo from "./MyCompo.js";
class App extends React.Component{
    constructor(){
        super();
    }

    render(){
        return (
            <div>
                <MyCompo a="66" b="77" c="88"></MyCompo>
            </div>
        )
    }
}

//向外暴露
export default App;

子组件:

import React from "react";
class MyCompo extends React.Component{
    constructor(){
        super();
    }
    render(){
        return (
            <div>
                我是MyCompo组件
                {/*子组件中就可以使用this.props来枚举父组件传下来的值*/}
                <p>{this.props.a}</p>
                <p>{this.props.b}</p>
                <p>{this.props.c}</p>
            </div>
        );
    }
}

//向外暴露
export default MyCompo;

如果是要在构造函数中使用父组件传下来的props,此时React内部会将props作为构造函数的第一个参数传进来

class MyCompo extends React.Component{
    constructor(props){
        super();
        this.state = {
        //接收系统注入的父组件的属性值为自己的local状态
            c : props.c
        }
    }
 }

注意:props在子组件中,依然是只读的。不能修改。如果想要修改,只能用state来接受,然后修改state。
有时候难以避免,子组件非要和父组件通信,那只能通过父组件向下传递一个函数,子组件通过传参的形式,调用父组件的函数,然后将数据返回给父组件的函数,父组件的函数接收到实参之后,改变父组件自己内部的state。

import React from "react";
import MyCompo from "./MyCompo.js";
class App extends React.Component{
    constructor(){
        super();

        this.state = {
            d : 16
        }
    }

    setD(number){
        this.setState({"d" : number});
    }

    render(){
        return (
            <div>
                <p>我是App组件,我有一个d状态:{this.state.d}</p>
                <MyCompo setD={(this.setD).bind(this)} d={this.state.d}></MyCompo>
            </div>
        )
    }
}

//向外暴露
export default App;

子组件就要接受父组件传来的d参数和设置D的函数:

import React   from "react";
import { PropTypes } from "prop-types";
class MyCompo extends React.Component{
    constructor(props){
        super();

        this.state = {
            d : props.d
        }

        this.add = () =>{
            this.setState({"d" : this.state.d + 1});
            props.setD(this.state.d + 1);
        }
    }


    render(){
        return (
            <div>
                <hr/>
                我是MyCompo组件
                <p>d : {this.state.d}</p>
                <p>
                    <input type="button" value="按我更改d的值" onClick={this.add}/>
                </p>
            </div>
        );
    }
}
//定义组件需要传入的参数
//props属性是可以被验证有效性的,也就是说规定你要传给我什么样的类型,就必须是什么样的类型。否则无效。
//需要yarn add prop-types -D
MyCompo.propTypes = {
    a : PropTypes.string.isRequired,
    b : PropTypes.string.isRequired,
    c : PropTypes.number.isRequired
};

//向外暴露
export default MyCompo;

目前看起来还可以接收,但是如果要和孙子,重孙子组件通信,怎么办呢,虽然可以一层一层的通过props往下传递,但是组件嵌套太深,既难以维护,编写有麻烦。所以将会在后续的文章中介绍redux,状态管理。

Tips:如果通过props传递引用类型数据,此时也是不会颠覆“数据单向传递”的限制。子组件中对数组、JSON对象的改变,不会引起父组件中那个数组、JSON对象的改变,可以认为传入了副本。

三基友之末:context

context又称上下文, 其精髓是可以跨级传递数据,爷爷组件可以直接传递数据到孙子组件。

爷爷组件(哈哈哈哈)

import React from "react";
import Baba from "./Baba.js";
import PropTypes from "prop-types";
//为了方便理解,就用拼音啦。
class Yeye extends React.Component{
    constructor(){
        super();
        this.state = {
            a : 100
        }
    }

    render(){
        return (
            <div>
                <h1>爷爷</h1>
                <Baba></Baba>
            </div>
        );
    }

    //得到孩子上下文,实际上这里表示一种设置,返回一个对象,这个对象就是现在这个家族体系共享的上下文。将上下文中的a值变为自己的状态中的a值
    getChildContext(){
        return {
            a : this.state.a
        }
    }
}

//设置child的上下文类型
Yeye.childContextTypes = {
    a : PropTypes.number.isRequired
}

export default Yeye;

孙子组件(哈哈哈哈)

import React from "react";
import PropTypes from "prop-types";

class Sunzi extends React.Component{
    //React会将上下文当做构造函数的第二个参数传入:
    constructor(props,context){
        super();
        console.log(context);  //得到上下文
    }

    render(){
        return (
            <div>
                <h1>孙子</h1>
            </div>
        );
    }
}

//设置上下文的类型
Sunzi.contextTypes = {
    a : PropTypes.number
}

export default Sunzi;

结论:
1) 当祖先元素中更改了上下文的数据,此时所有的子孙元素中的数据都会更改,视图也会更新;
2) 反之不成立,可以认为上下文的数据在子孙元素中是只读的。此时又要需要使用奇淫技巧,就是在context中共享一个操作祖先元素的函数,子孙元素通过上下文获得这个函数,从而操作祖先元素的值。也就是说,state是自治的不涉及传值的事儿;props是单向的,父亲→儿子;context也是单向的,祖先→后代。如果要反向,就要传入一个函数。
基本很少使用这个api,但是Redux底层使用context实现.

猜你喜欢

转载自www.cnblogs.com/tdd-qdkfgcs/p/11082017.html