React 入门

2017-05-22  本文已影响1409人  贾里

1.React 的学习资源

2.React

什么是React?

React 特点

什么是JSX?
React 使用 JSX(JavaScript XML) 来替代常规的 JavaScript。JSX 是一个看起来很像 XML 的 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的好处:

缺点:

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组件生命周期

实例化

首次实例化

实例化完成后的更新

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

销毁&清理期

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

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

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

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

4.render
必选的方法,创建虚拟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.表单提交和函数复用

案例:

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

上一篇下一篇

猜你喜欢

热点阅读