美文网首页
React 入门

React 入门

作者: 贾里 | 来源:发表于2017-05-22 22:07 被阅读1409次

    1.React 的学习资源

    2.React

    什么是React?

    • React 是一个用于构建用户界面的 JAVASCRIPT 库。
    • React主要用于构建UI,很多人认为 React 是 MVC 中的 V(视图)。
    • React 起源于 Facebook 的内部项目,用来架设 Instagram 的网站,并于 2013 年 5 月开源。
    • React 拥有较高的性能,代码逻辑非常简单,越来越多的人已开始关注和使用它。
    • 既可以开发浏览器应用,又可以开发移动应用

    React 特点

    • 1.声明式设计 −React采用声明范式,可以轻松描述应用。
    • 2.高效 −React通过对DOM的模拟,最大限度地减少与DOM的交互。
    • 3.灵活 −React可以与已知的库或框架很好地配合。
    • 4.JSX − JSX 是 JavaScript 语法的扩展。React 开发不一定使用 JSX ,但我们建议使用它。
    • 5.组件 − 通过 React 构建组件,使得代码更加容易得到复用,能够很好的应用在大项目的开发中。
    • 6.单向响应的数据流 − React 实现了单向响应的数据流,从而减少了重复代码,这也是它为什么比传统数据绑定更简单。

    什么是JSX?
    React 使用 JSX(JavaScript XML) 来替代常规的 JavaScript。JSX 是一个看起来很像 XML 的 JavaScript 语法扩展。我们不需要一定使用 JSX,但它有以下优点:

    • JSX 执行更快,因为它在编译为 JavaScript 代码后进行了优化。
    • 它是类型安全的,在编译过程中就能发现错误。
    • 使用 JSX 编写模板更加简单快速。

    3.React的HelloWorld

    打开网址:https://facebook.github.io/react/docs/installation.html
    官网中有如何搭建React的Helloworld。
    index.html:

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
        <script src="https://unpkg.com/react@latest/dist/react.js"></script>
        <script src="https://unpkg.com/react-dom@latest/dist/react-dom.js"></script>
        <script src="https://unpkg.com/babel-standalone@6.15.0/babel.min.js"></script>
        <script type="text/babel" src="01.js"></script>
    
    </head>
    <body>
    <div id="root"></div>
    
    </body>
    </html>
    

    01.js:

    //React组件化(可以重用),即自定义组件
    class TextCompat extends React.Component{
        //组件的内容
        render(){
            return <div>Hello world!!!</div>
        }
    }
    
    
    class WrapperText extends React.Component{
        render(){
            //虚拟DOM(Document Object Model)
            //html标签,小写开头
            //自定义组件:大写开头
            return <p>
                <TextCompat></TextCompat>
                <span>jarry</span>
            </p>
        }
    }
    
    
    //绘制到页面中
    //ReactDOM.render(<WrapperText></WrapperText>, document.body);
    ReactDOM.render(<TextCompat/>, document.getElementById('root'));
    

    流程:
    1.编写组件
    2.绘制到页面

    ReactDOM.render(<TextCompat/>, document.getElementById('root'));
    

    虚拟DOM
    DOM:html标签
    虚拟DOM即包括DOM又嵌套组件,和普通DOM的区别:html标签是小写,组件是大写开头
    虚拟DOM的好处:

    • 1.跨平台(讲虚拟DOM翻译成对应平台的语言)
    • 2.组件化管理,适合大型项目

    缺点:

    4.React的状态

    State(状态)
    React 把组件看成是一个状态机(State Machines)。通过与用户的交互,实现不同状态,然后渲染 UI,让用户界面和数据保持一致。
    React 里,只需更新组件的 state,然后根据新的 state 重新渲染用户界面(不要操作 DOM)。
    不需要直接操作DOM,而是通过修改state,自动更新界面

    1.初始化状态
    2.改变状态
    3.获取状态,当状态改变的时候,会重绘页面

    注意需要安装React dev tools的插件,来查看错误日志
    需要用React.createClass,而不是用继承的方式,因为这个会走生命周期流程。

    案例:
    点击切换,喜欢不喜欢?

    //实例化组件对象过程中,调用getInitialState设置state对象的初始值
    var Text = React.createClass({
        //设置状态的初始值
        getInitialState : function(){
            return {isLike:false};
        },
        //点击事件回调
        handleClick : function(){
            //改变状态
            this.setState({isLike:!this.state.isLike});
        },
        //当状态改变时,会重新调用render函数,重绘
        render : function(){
            //获取状态
            var text = this.state.isLike ? "喜欢" : "不喜欢";
            return <p onClick={this.handleClick}>你{text}吗?</p>
        }
    });
    
    ReactDOM.render(<Text/>, document.getElementById("myDiv"));
    

    上面例子的加载流程:
    1.getInitialState(初始化,设置组件的state的值,给了一个对象)
    2.render(不喜欢)

    点击之后的流程
    1.handleClick(点击回调)
    2.setState(改变状态)
    3.render(喜欢)

    5.Props(属性)

    state 和 props 主要的区别在于 props 是不可变的,而 state 可以根据与用户交互来改变。这就是为什么有些容器组件需要定义 state 来更新和修改数据。 而子组件只能通过 props 来传递数据。
    可以通过 getDefaultProps() 方法为 props 设置默认值

    var Text = React.createClass({
        render : function(){
            return <p>I love {this.props.name}</p>;
        }
    });
    
    ReactDOM.render(<Text name="Jason"/>, document.getElementById("myDiv"));
    

    案例:
    实现在输入框中,输入内容,上面的文字控件也跟着改变

    //子组件
    var TextComponent = React.createClass({
        render : function(){
            return <div>Hello {this.props.text}!</div>;
        }
    
    });
    
    //父组件
    var WrapperComponent = React.createClass({
        //state初始化
        getInitialState : function(){
            return {text:''};
        },
        //内容改变回调
        handleChange : function(e){
            //e 是Event事件对象,target是指事件的目标对象
            //改变状态
            this.setState({text:e.target.value});
        },
        render : function(){
            return <div>
                <TextComponent text={this.state.text}/>
                <input type="text" onChange={this.handleChange}/>
            </div>;
        }
    
    });
    
    ReactDOM.render(<WrapperComponent/>, document.getElementById("myDiv"));
    

    例子中传参流程
    1.handleChange(父组件回调)
    2.setState(修改父组件的状态)
    3.render(父组件重新渲染,子组件也会渲染)
    3.把父组件的状态值,作为子组件的属性值传入
    4.render(子组件内容改变)

    6.React组件生命周期

    实例化

    首次实例化

    • getDefaultProps
    • getInitialState
    • componentWillMount
    • render
    • componentDidMount

    实例化完成后的更新

    • getInitialState
    • componentWillMount
    • render
    • componentDidMount

    存在期
    组件已存在时的状态改变

    • componentWillReceiveProps
    • shouldComponentUpdate
    • componentWillUpdate
    • render
    • componentDidUpdate

    销毁&清理期

    • componentWillUnmount

    7.生命周期共提供了10个不同的API。

    1.getDefaultProps
    作用于组件类,只调用一次,返回对象用于设置默认的props,对于引用值,会在实例中共享。

    2.getInitialState
    作用于组件的实例,在实例创建时调用一次,用于初始化每个实例的state,此时可以访问this.props。

    3.componentWillMount
    在完成首次渲染之前调用,此时仍可以修改组件的state。

    4.render
    必选的方法,创建虚拟DOM,该方法具有特殊的规则:

    • 只能通过this.props和this.state访问数据
    • 可以返回null、false或任何React组件
    • 只能出现一个顶级组件(不能返回数组)
    • 不能改变组件的状态
    • 不能修改DOM的输出

    5.componentDidMount
    真实的DOM被渲染出来后调用,在该方法中可通过this.getDOMNode()访问到真实的DOM元素。此时已可以使用其他类库来操作这个DOM。
    在服务端中,该方法不会被调用。

    6.componentWillReceiveProps
    组件接收到新的props时调用,并将其作为参数nextProps使用,此时可以更改组件props及state。

        componentWillReceiveProps: function(nextProps) {
            if (nextProps.bool) {
                this.setState({
                    bool: true
                });
            }
        }
    

    7.shouldComponentUpdate
    组件是否应当渲染新的props或state,返回false表示跳过后续的生命周期方法,通常不需要使用以避免出现bug。在出现应用的瓶颈时,可通过该方法进行适当的优化。
    在首次渲染期间或者调用了forceUpdate方法后,该方法不会被调用

    8.componentWillUpdate
    接收到新的props或者state后,进行渲染之前调用,此时不允许更新props或state。

    9.componentDidUpdate
    完成渲染新的props或者state后调用,此时可以访问到新的DOM元素。

    10.componentWillUnmount
    组件被移除之前被调用,可以用于做一些清理工作,在componentDidMount方法中添加的所有任务都需要在该方法中撤销,比如创建的定时器或添加的事件监听器。

    8.传参

    通过属性Props可以在父组件中给子组件设置属性值,也就是将父组件的值传递给子组件。但是要解决子组件访问父组件的方法,可以通过回调函数解决,这就和Java的接口一样。
    他的主要思路:子组件委托父组件处理。

    下面通过一个案例来实现

    • 下拉列表(子组件)的数据发生改变,表单(父组件)能够获取到,用于后面的提交子组件向父组件传递参数

    1.下拉选择组件:

    //表单的子组件
    var ChildGenderSelect = React.createClass({
        render : function(){
            return <select onChange={this.props.handleSelectChange}>
                <option value="1">男</option>
                <option value="0">女</option>
            </select>;
        }
    });
    

    2.表单的组件:

    //提交数据的两种做法:
    //1.直接提交表单,跳转,整个页面刷新(过时的做法)
    //2.屏蔽表单的默认提交行为,通过ajax提交数据,服务器响应成功之后在跳转(类似于Android)
    var ParentForm = React.createClass({
        getInitialState : function(){
            return {gender:0};
        },
        handleChange : function(e){
            //保存到state
            this.setState({gender:e.target.value});
        },
        handleSubmit : function(e){
            //屏蔽表单的默认提交行为
            e.preventDefault();
            //ajax发起请求,提交数据
            /*var request = new XMLHttpRequest();
            request.open("GET","test.json?gender="+this.state.gender);
            request.onreadystatechange = handler;
            request.responseType = "json"; //服务器响应数据的类型 json
            request.setRequestHeader("Accept","application/json");
            request.send();
    
            //回调
            function handler(){
                //4(完成)响应内容解析完成
                if(this.readyState !== 4){
                    return;
                }
                if(this.status == 200){
                    //请求成功:显示数据
                    console.log("ok");
                }
            }*/
        },
        render : function () {
            return <form onSubmit={this.handleSubmit}>
                <ChildGenderSelect handleSelectChange={this.handleChange}></ChildGenderSelect><br/>
                <button type="submit">提交</button>
            </form>;
        }
    });
    

    3.将表单组件渲染到页面:

    ReactDOM.render(<ParentForm></ParentForm>,document.body);
    

    4.案例的流程:
    (1).通过handleSelectChange属性,传入父组件的handleChange函数给子组件。
    (2).当子组件的onChange事件触发,调用this.props.handleSelectChange->父组件的handleChange函数。

    9.表单提交和函数复用

    案例:

    • 点击提交按钮,提交输入框输入的内容,通过Ajax请求提交

    1.方式一:不可控组件
    主要思路:通过ref获取组件

    var FormComponent = React.createClass({
        //处理表单提交
        handleSubmit : function(e){
            e.preventDefault();
            //ref类似于id,是一个唯一标示 React.findDOMNode
            var text = this.refs.input_name.value;
            alert(text);
        },
        render : function(){
            return <form onSubmit={this.handleSubmit}>
                <input type="text" ref="input_name" defaultValue="Hello..."/><br/>
                <button type="submit">提交</button>
            </form>;
        }
    });
    
    ReactDOM.render(<FormComponent></FormComponent>,document.body);
    

    2.方式二:可控组件(和MVVM data binding很类似)
    主要思路:把数据存储在状态中(通过事件监听,进行数据绑定),需要时,从状态中获取

    var FormComponent = React.createClass({
        getInitialState : function () {
            return {text:''};
        },
        handleChange : function(e){
            //保存到state
            this.setState({text:e.target.value});
        },
        handleSubmit : function(e){
            e.preventDefault();
            alert(this.state.text);
        },
        render : function(){
            return <form onSubmit={this.handleSubmit}>
                <input type="text" defaultValue="Hello..." onChange={this.handleChange}/><br/>
                <button type="submit">提交</button>
            </form>;
        }
    });
    
    ReactDOM.render(<FormComponent></FormComponent>,document.body);
    

    随之产生的问题:多个控件需要多个事件处理函数,如何做到事件处理函数的复用?

    3.方式三:事件处理函数的复用方式1
    在方式二的基础上进行改进,让子控件可以实现复用,并保存对应的属性值。
    思路:bind返回改变了上下文this后的函数

    var FormComponent = React.createClass({
        getInitialState : function () {
            return {
                username:'',
                gender:0,
                agree:true
            };
        },
        //key ->'username' ....
        handleChange : function(key,e){
            //this->FormComponent对象
            //保存到state
            var obj = {};
            obj[key] = e.target.value;
            this.setState(obj);
        },
        handleSubmit : function(e){
            e.preventDefault();
            console.log(this.state);
        },
        //多个控件的事件响应,共用这个函数
        render : function() {
            return <form onSubmit={this.handleSubmit}>
                <label htmlFor="username">输入用户名:</label>
                <input id="username" type="text" onChange={this.handleChange.bind(this,'username')}/><br/>
                <label htmlFor="gender">请选择性别:</label>
                <select id="gender" onChange={this.handleChange.bind(this,'gender')}>
                    <option value="1">男</option>
                    <option value="0">女</option>
                </select><br/>
                <label htmlFor="agree">是否同意:</label>
                <input id="agree" type="checkbox" onChange={this.handleChange.bind(this,'agree')} /><br/>
                <button type="submit">提交</button>
            </form>;
        }
    
    });
    
    ReactDOM.render(<FormComponent></FormComponent>,document.body);
    

    注意:this的作用域问题,如果this指定的函数是非箭头函数,会导致作用域不正确,子控件拿不到对应的函数和属性值。

    4.方式四:事件处理函数的复用方式2
    思路:指定属性,比如说name属性区分不同的组件

    var FormComponent = React.createClass({
        getInitialState : function () {
            return {
                username:'',
                gender:0,
                agree:true
            };
        },
    
        handleChange : function(e){
            var obj = {};
            obj[e.target.name] = e.target.value;
            this.setState(obj);
        },
        handleSubmit : function(e){
            e.preventDefault();
            console.log(this.state);
        },
        //多个控件的事件响应,共用这个函数
        render : function() {
            return <form onSubmit={this.handleSubmit}>
                <label htmlFor="username">输入用户名:</label>
                <input id="username" type="text" name="username" onChange={this.handleChange}/><br/>
                <label htmlFor="gender">请选择性别:</label>
                <select id="gender" name="gender" onChange={this.handleChange}>
                    <option value="1">男</option>
                    <option value="0">女</option>
                </select><br/>
                <label htmlFor="agree">是否同意:</label>
                <input id="agree" name="agree" type="checkbox" onChange={this.handleChange} /><br/>
                <button type="submit">提交</button>
            </form>;
        }
    
    });
    
    ReactDOM.render(<FormComponent></FormComponent>,document.body);
    

    10.动画

    案例:让一个文字,在3秒之类从左到右移动一段距离
    思路:用定时器,不断改变属性值,并渲染。

    var MyComponent = React.createClass({
        getDefaultProps : function(){
            return {
                position:100, //marginLeft目标值,组件实现可配置
                time:10
            };
        },
        getInitialState : function () {
            return {position:0};  //marginLeft开始值
        },
        render : function(){
            var style = {
                color:'red',
                marginLeft:this.state.position //左外边距
            };
            return <div style={style}>This will animated!</div>;
        },
        //动画函数,不断改变state的position属性
        transformComponnent : function () {
            if(this.state.position < this.props.position){
                this.setState({
                    position : ++this.state.position
                });
            }else{
                //动画完成,取消定时器
                clearInterval(this.timer);
            }
            console.log("transformComponnent");
        },
        //渲染完成
        componentDidMount : function () {
            //开启定时器
            this.timer = setInterval(this.transformComponnent,this.props.time);
        }
    });
    
    
    ReactDOM.render(<MyComponent position={200} time={20}></MyComponent>,document.body);
    

    流程
    1.初始化控件,执行生命周期中的getDefaultProps 和getInitialState ;
    2.控件渲染完成,执行componentDidMount ;
    3.开启定时器,不断执行transformComponnent ;
    4.保存状态setState;
    5.不断渲染render,渲染的时候marginLeft拿取状态值;

    11.计时器案例

    1.模块化
    把一些类,函数,变量写到一个文件中,作为一个功能模块,当要使用这个模块,只需要引用。
    nodejs不支持,用Bable6转es6为es5(js库,实现模块化),es6模块化(新的语法特性),node和chrome都不支持。

    2.创建react工程
    支持语法特性,非常便利

    npm install -g create-react-app
    create-reate-app test
    cd test
    

    3.模块引入和导出
    创建一个组件
    class TimeDisplay extends React.Component{
    }

    引入:import React form 'react';

    导出:export default TimeDisplay;

    4.代码

    this只有在运行时才确定
    转为箭头函数,因为this的作用域是最外层的,比普通的function少了绑定this步骤;
    在构造函数中对状态赋初始值,不能调用setState
    TimeDisplay.js:

    import React from 'react';
    
    import DisplayLog from './DisplayLog';
    import Button from './Button';
    import formatTime from '../utils/formatTime';
    
    class TimeDisplay extends React.Component{
        //在构造函数中对状态赋初始值
        constructor(props){
            super(props);
            //不能调用state
            this.state = {
                time:0,
                on:false,
                log:[] //数组
            };
        }
    
        //开始按钮
        handleToggle = ()=>{
            //已经开始,取消正在运行的定时器
            if(this.state.on){
                clearInterval(this.timer);
            }else{
                //否则开启
                //定时器,10ms累加1
                this.timer = setInterval(()=>this.setState({time:this.state.time+1}),10);
            }
            this.setState({on:!this.state.on});
        }
        //记录时间
        handleLogTime = ()=>{
            //存储在数组中
            this.state.log.push(this.state.time);
        }
        //清空记录
        handleClearLog = ()=>{
            this.setState({log:[]});
        }
        //重置
        handleReset = () =>{
            this.setState({time:0});
        }
    
        render(){
            var time = formatTime(this.state.time);
            return <div>
                <h1 className="display-time">{time}</h1>
                <div className="controls">
                    <Button className={this.state.on ? "danger" : "success"} text={this.state.on ? "暂停" : "开始"} onClick={this.handleToggle}/>
                    <Button className="warning"  onClick={this.handleReset}/>
                    <Button className="primary" text="记录" onClick={this.handleLogTime}/>
                    <Button className="undefined" text="清空" onClick={this.handleClearLog}/>
                </div>
                <DisplayLog log={this.state.log}/>
            </div>;
        }
    
        //监听键盘事件
        componentDidMount(){
            //内置对象
            window.addEventListener('keydown', e => e.preventDefault());
            window.addEventListener('keyup', e => {
                e.preventDefault();
                switch (e.keyCode){
                    case 32: //space
                        this.handleToggle();
                        break;
                    case 82:
                        this.handleReset();
                        break;
                    case 13:
                        this.handleLogTime();
                        break;
                    case 67:
                        this.handleClearLog();
                        break;
                    default:
                        break;
                }
            });
        }
    
        //组件被移除,事件监听取消
        componentWillUnmount(){
            window.removeEventListener('keydown');
            window.removeEventListener('keyup');
        }
    
    }
    
    //在外部要使用TimeDisplay
    export default TimeDisplay;
    

    Button.js:
    button的封装,数据、样式、事件的传递

    import React from 'react';
    
    class Button extends React.Component{
        //静态属性,给属性赋默认值
        static defaultProps = {
            onClick : null,
            className : '',
            text : '默认'
        };
    
        render(){
            return <button className={`Button ${this.props.className}`} onClick={this.props.onClick}>{this.props.text}</button>;
        }
    }
    
    export default Button;
    

    DisplayLog.js:

    import React from 'react';
    import formatTime from '../utils/formatTime';
    
    class DisplayLog extends React.Component{
        //log数组长度等于0,返回“空空如也”
        renderEmpty = () =>{
            return <span className="empty-log">空空如也...</span>;
        }
    
        //否则,把元素遍历(时间),得到一系列的<li>
        renderLog = () => {
            //<li>00</li>
            //<li>11</li>
            return this.props.log.map(item => {
                return <li className="log-item" >{formatTime(item)}</li>;
            });
        }
    
        render(){
            //log数组长度等于0,返回“空空如也”
            //否则,把元素遍历(时间),得到一系列的<li>
            //<ul>
            //  <li>00</li>
            //  <li></li>
            //</ul>
            const log = this.props.log.length === 0 ? this.renderEmpty() : this.renderLog();
            return <ul className="log">
                {log}
            </ul>;
        }
    }
    
    export default DisplayLog;
    

    formatTime.js:

    //将数组转为字符串,如果数字只有一位,会在第一个位置添加一个0
    //9->9->09
    const appendZero = n => n.toLocaleString({},{minimumIntegerDigits:2});
    
    export default function(t = 0){
        const msec = appendZero(t % 100),
            sec = appendZero(parseInt((t/100) % 60)),
            min = appendZero(parseInt((t/6000) % 60)),
            hour = appendZero(parseInt(t/360000));
        return `${hour}:${min}:${sec}.${msec}`;
    }
    
    

    App.css

    .App {
      text-align: center;
    }
    
    ul,li{
      list-style: none; /*去掉列表默认样式*/
      margin: 0px;
      padding: 0px;
    }
    
    .display-time{
      font-size: 45px;
      font-weight: bold;
      margin: 60px 0px;
    }
    
    .controls{
      background-color: #eee;
    }
    
    .Button{
      width: 130px;
      font-size: 20px;
      padding: 10px 20px;
      margin: 20px;
      border: none;
      border-radius: 4px;
      background-color: lightgreen;
      cursor: pointer;
    }
    
    .Button:hover{
      /*CSS3阴影*/
      box-shadow: 0 0 25px rgba(0,0,0,0.2);
    }
    
    .success{
      background-color: lightgreen;
    }
    .danger{
      background-color: hotpink;
    }
    .warning{
      background-color: gold;
    }
    .primary{
      background-color: skyblue;
    }
    .undefined{
      background-color: #e1e1e1;
    }
    
    .log{
      text-align: center;
      font-size: 30px;
      color: #e1e1e1;
    }
    
    /*log和empty-log都会应用这个样式*/
    .log .empty-log{
      display: inline-block;
      padding: 50px 10px;
    }
    
    /*.log类下的li标签 */
    /*.log > li{*/
    .log-item{
      font-size: 25px;
      color: #000;
      border-bottom: 1px solid #eee;
      padding: 10px 0px;
    }
    
    /*伪类*/
    /*悬浮*/
    .log-item:hover{
      background-color: rgba(238, 238, 238, 0.5);
    }
    

    App.js

    import React from 'react';
    import './App.css';
    import TimeDisplay from './components/TimeDisplay'
    
    class App extends React.Component {
      render() {
        return <div className="App">
          <TimeDisplay />
        </div>;
      }
    }
    
    export default App;
    
    

    12.常用的补充点

    1.数组
    数组的创建

    var a = [];
    var b = new Array();
    

    数组的元素赋值

    a[0] = 2;
    a[1] = 10;
    a.push(30);
    

    清空

    console.log(a);
    a = [];
    console.log(a);
    

    map 遍历数组的函数

    //item是数组的元素,i数组的索引,b是返回的数组
    var b = a.map(function(item,i){
        return ++item;
    });
    console.log(b);
    

    2.组件的两种创建方式
    1.React.createClass
    对应的属性和状态初始化方法:

    getDefaultProps
    getInitialState
    

    2.extends Component
    对应的属性和状态初始化方法:

    static defaultProps
    constructor
    

    3.css样式
    .log-item

    相关文章

      网友评论

          本文标题:React 入门

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