React技术栈系列—基础01

Posted tdd-qdkfgcs

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了React技术栈系列—基础01相关的知识,希望对你有一定的参考价值。

技术图片

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实现.

以上是关于React技术栈系列—基础01的主要内容,如果未能解决你的问题,请参考以下文章

SignalR在React/Go技术栈的实践

深入react技术栈:React组件

深入react技术栈:React组件

深入react技术栈:React生命周期

深入react技术栈:React生命周期

深入react技术栈:React数据流