React入门笔记(二):组件和AJAX

React入门笔记(一):简介和JSX
React入门笔记(二):组件和AJAX
React入门笔记(三):表单、事件、Ref属性
React中的Diff算法——Christopher Chedeau(原文翻译)

一、React组件

React允许将代码封装成组件,然后就像插入普通HTML标签一样,在网页中插入这个组件。
在上一章节介绍JSX时,对React组件进行了简单的介绍,包含React组件的三种创建方式:React.createClass()ES6 class和无状态函数。
使用React.createClass()方法生成一个组件类举例如下:

<div id="example"></div>
<script type="text/babel">
    var MyComponent = React.createClass({
        render: function(){
            return <h1>Hello {this.props.name}</h1>;
        }
    });
    React.render(
        <MyComponent name="React" />
        document.getElementById('example')
    );
</script>

上面简单的栗子中,变量MyComponent就是一个组件类。模板插入<MyComponent />时,会自动生成MyComponent的一个实例,所有的组件类都必须有自己的render方法,用于输出组件。注意组件类的第一个字母必须大写,用于与普通HTML标签区别,否则会报错。另外,组件类只能包含一个顶层标签(这是XML的特点,即有且仅有一个根标签),否则也会报错。
组件的用法与原生的HTML标签一致,可以任意加入属性。组件的属性可以在组件类的this.props对象上获取,比如name属性可以通过this.props.name读取,添加组件属性中一个特殊点需要注意:class属性需要写成classNamefor属性需要写成htmlFor,这是因为classfor是JavaScript的保留字。
我们可以通过创建多个组件来合成一个组件,这样把组件的不同功能点进行分离,以下栗子实现了输出网站名称和网址的组件:

class WebSite extends React.Component{
    render: function(){
        return (
            <div>
                <Name name={this.props.name} />
                <Link site={this.props.site} />
            </div>
        );
    }
}
var Name = React.createClass({
    render: function(){
        return (
            <h1>{this.props.name}</h1>
        );
    }
});
var Link = React.createClass({
    render: function(){
        return (
            <a href={this.props.site}>
                {this.props.site}
            </a>
        );
    }
});
ReactDOM.render(
    <WebSite name="百度" site="www.baidu.com" />,
    document.getElementById('example')
);

二、React状态

React把组件看成一个状态机(State Machine),组件免不了要与用户互动,React的一大创新就是将组件看成是一个状态机,一开始有初始状态,然后用户互动,导致状态变化,从而触发重新渲染UI。通过与用户的交互实现不同状态,然后渲染UI,让用户界面和数据保持一致。React李只需要更新组件的state,然后根据新的state重新渲染用户界面(不要操作DOM)。
在微博等社交平台常见的like按钮,可以通过如下的一个栗子实现。创建了一个LikeButton组件,getInitialState方法用于定义初始状态,也就是一个对象,这个对象可以通过this.state属性读取。当用户点击组件,导致状态变化,this.setState方法就修改状态值,每次修改以后,自动调用this.render方法,再次渲染组件。

var LikeButton = React.createClass({
    // 定义初始状态,也就是一个对象,这个对象可以通过`this.state`属性读取
    getInitialState: function(){
        return {liked: false};
    },
    handleClick: function(event) {
        // 当用户点击组件导致状态变化,`this.setState`方法就修改状态值
        // 每次修改后,自动调用`this.render`方法,再次渲染组件
        this.setState({liked: !this.state.liked});
    },
    render: function() {
        var text = this.state.liked? '喜欢':'不喜欢';
        return (
            // onClick等事件与原生HTML不同,
            // on之后的第一个字母是大写,
            // onclick是无效的 
            <p onClick={this.handleClick}>
                <b>{text}</b>:点击切换状态!
            </p>
        );
    }
});
ReactDOM.render(
    <LikeButton />,
    document.getElementById('example')
);

三、React Props

  1. Props的使用
    stateprops主要区别是props是不可变的,而state可以根据与用户交互来改变。 有些容器组件需要定义state来更新和修改数据,而子组件通常通过props来传递数据。简单的栗子如下:

    var HelloMessage = React.createClass({
        // 为props设置默认值
        getDefaultProps: function() {
            return {
                name: 'Sussi'
            };
        },
        render: function() {
            return <h1>Hello {this.props.name}</h1>;
        }
    });
    ReactDOM.render(
        <HelloMessage />,
        document.getElementById('example')
    );
  2. State和Props
    stateprops常常在应用中组合使用,可以在父组件中设置state,并通过在子组件上使用props将其传递到子组件上。在render函数中,我们设置namesite来获取父组件传递过来的数据。

    var WebSite = React.createClass({
        getInitialState: function(){
            return {
                name: '百度',
                site: 'http://baidu.com'
            };
        },
        render: function() {
            return (
                <div>
                    <Name name={this.state.name} />
                    <Link site={this.state.site} />
                </div>
            );
        }
    });
    var Name = React.createClass({
        render: function() {
            return (
                <h1>{this.props.name}</h1>
            );
        }
    });
    var Link = React.createClass({
        render: function() {
            return (
                <a href={this.props.site}>
                    {this.props.site}
                </a>
            );
        }
    });
    ReactDOM.render(
        <WebSite />,
        document.getElementById('example')
    );
  3. Props验证
    Props验证使用propTypes,它可以保证我们的应用组件被正确使用,React.PropTypes提供很多验证器(validator)来验证传入数据是否有效。当向props传入无效数据时,JavaScript控制台会发出警告。一个简单的栗子如下,验证title属性必须为字符串:

    var title1 = "测试";
    // var title1 = 123;
    var MyTitle = React.createClass({
        propTypes: {
            title: React.PropTypes.string.isRequired,
        },
        render: function(){
            return <h1>{this.props.title}</h1>
        }
    });
    ReactDOM.render(
        <MyTitle title={title1} />,
        document.getElementById('example')
    );

    更多常见的验证器说明如下:

    React.createClass({
        propTypes: {
            // 可以声明prop为指定的JS基本数据类型,默认情况,这些数据是可选的
            optionalArray: React.PropTypes.array,
            optionalBool: React.PropTypes.bool,
            optionalFunc: React.PropTypes.func,
            optionalNumber: React.PropTypes.number,
            optionalObject: React.PropTypes.object,
            optionalString: React.PropTypes.string,
    
            // 可以被渲染的对象numbers,strings,elements或array
            optionalNode: React.PropTypes.node,
    
            // React 元素
            optionalElement: React.PropTypes.element,
    
            // 用JS的instanceof操作符声明prop为类的实例
            optionalMessage: React.PropTypes.instanceOf(Message),
    
            // 用enum来限制prop只接受指定的值
            optionalEnum: React.PropTypes.oneOf(['News', 'Photos']),
    
            // 可以是多个对象类型中的一个
            optionalUnion: React.PropTypes.oneOfType([
                React.PropTypes.string,
                React.PropTypes.number,
                React.PropTypes.instanceOf(Message)
            ]),
    
            // 指定类型组成的数组
            optionalArrayOf: React.PropTypes.arrayOf(React.PropTypes.number),
    
            // 指定类型的属性构成的对象
            optionalObjectOf: React.PropTypes.objectOf(React.PropTypes.number),
    
            // 特定shape参数的对象
            optionalObjectWithShape: React.PropTypes.shape({
                color: React.PropTypes.string,
                fontSize: React.PropTypes.number
            }),
    
            // 任意类型加上`isRequired`来使prop不可空
            requiredFunc: React.PropTypes.func.isRequired,
    
            // 不可空的任意类型
            requiredAny: React.PropTypes.any.isRequired,
    
            // 自定义验证器。若验证失败需要返回一个Error对象,不要直接使用`console.warn`或抛异常,因为这样`oneOfType`会失效。
            customProp: function(props, propName, componentName) {
                if(!/matchme/.test(props[propName])){
                    return new Error('Validation failed!');
                }
            }
        },
        /* ... */
    });

四、React组件API

React组件有以下7个方法:

  • 设置状态:setState
  • 替换状态:replaceState
  • 设置属性:setProps
  • 替换属性:replaceProps
  • 强制更新:forceUpdate
  • 获取DOM节点:findDOMNode
  • 判断组件挂载状态:isMounted

==================================================

  1. 设置状态:setState

    setState(object nextState[, function callback])

    参数说明:

    • nextState,将要设置的新状态,该状态会和当前的state合并;
    • callback,可选参数,回调函数。该函数会在setState设置成功,且组件重新渲染后调用。

    合并nextState和当前state,并重新渲染组件。setState是React事件处理函数中和请求回调函数中触发UI更新的主要方法
    关于setState:

    • 不能在组件内部通过this.state修改状态,因为该状态会在调用setState()后被替换。
    • setState()并不会立即改变this.state,而是创建一个即将处理的state。setState()并不一定是同步的,为了提升性能React会批量执行state和DOM渲染。
    • setState()总是会触发一次组件重绘,除非在shouldComponentUpdate()中实现了一些条件渲染逻辑。

    以下给出一个使用的栗子:

    var Counter = React.createClass({
        getInitialState: function(){
            return {clickCount: 0};
        },
        handleClick: function() {
            this.setState(function(state) {
                return {clickCount: state.clickCount+1};
            });
        },
        render: function() {
            return (
                <h3 onClick={this.handleClick}>
                    点我!点击次数为:{this.state.clickCount}
                </h3>
            );
        }
    });
    ReactDOM.render(
        <Counter />,
        document.getElementById('example')
    );
  2. 替换状态:replaceState

    replaceState(object nextState[, function callback])

    其中的参数含义与setState()相同。replaceState()方法与setState()类似,但是方法只会保留nextState中状态,原state不在nextState中的状态都会被删除。

  3. 设置属性:setProps

    setProps(object nextProps[, function callback])

    参数说明:

    • nextProps,将要设置的新属性,该状态会和当前的props合并;
    • callback,可选参数,回调函数。该函数会在setProps设置成功,且组件重新渲染后调用。

    设置组件属性,并重新渲染组件。
    props相当于组件的数据流,它总是从父组件向下传递至所有的子组件中。当和一个外部的JavaScript应用集成时,我们可能会需要向组件传递数据或通知React.render()组件重新渲染,可以使用setProps()
    更新组件,可以在节点上再次调用React.render(),也可以通过setProps()方法改变组件属性,触发组件重新渲染。

  4. 替换属性:replaceProps

    replaceProps(object nextProps[, function callback])

    replaceProps()方法与setProps类似,但是会删除原有的props。

  5. 强制更新:forceUpdate

    forceUpdate([function callback])

    参数说明:

    • callback,可选参数,回调函数。该函数会在组件render()方法调用后调用。

    forceUpdate()方法会使组件调用自身的render()方法重新渲染组件,组件的子组件也会调用自身的render()。但是组件重新渲染时,依然会读取this.propsthis.state若状态没有改变,则React只会更新DOM
    forceUpdate()方法适用于this.propsthis.state之外的组件重绘(如修改了this.state后),通过该方法通知React需要调用render()
    应当尽量避免使用forceUpdate(),而仅从this.propsthis.state中读取状态并有React触发render()调用。

  6. 获取DOM节点:findDOMNode

    DOMElement findDOMNode()
    • 返回值:DOM元素DOMElement

    如果组件已经挂载到DOM中,该方法返回对应的本地浏览器DOM元素。当render返回null或false时,this.findDOMNode()也会返回null。从DOM中读取值的时候,该方法会很有用,如:获取表单字段的值和做一些DOM操作。

  7. 判断组件挂载状态:isMounted

    bool isMounted()
    • 返回值:true或false,表示组件是否已挂载到DOM中

    isMounted()方法用于判断组件是否已挂载到DOM中。可以使用该方法保证setState()forceUpdate()在异步场景下的调用不会出错。

五、React组件生命周期

组件的生命周期可分为三个状态:

  • Mounting:已插入真实的DOM
  • Updating:正在被重新渲染
  • Unmounting:已移出真实DOM

React为每个状态都提供了willdid两种处理函数,will函数在进入状态之前调用,did函数在进入状态之后调用,三种状态共计5种处理函数(UNmount不需要did状态),如下:

  • componentWillMount:在渲染前调用,在客户顿也在服务端。
  • componentDidMount:在第一次渲染之后调用,只在客户端。之后组件已经生成了对应的DOM结构,可以通过this.getDOMNode()来进行访问。如果想和其他JavaScript框架一起使用,可以在这个方法中调用setTimeoutsetInterval或者发送AJAX请求等操作(防止异步操作阻塞UI)。
  • componentWillUpdate:在组件接收到新的props或者state但是还没有render时被调用。在初始化时不会调用。
  • componentDidUpdate:在组件完成更新后立即调用。在初始化时不会被调用。
  • componentWillUnmount:在组件从DOM中移除的时候立刻被调用。

此外还有两种特殊状态的处理函数:

  • componentWillReceiveProps:在组件接收到一个新的prop时被调用。这个方法在初始化render时不会被调用。
  • shouldComponentUpdate:返回一个布尔值。在组件接收到新的props或者state时被调用。在初始化时或者使用forceUpdate时不被调用。
    可以在你确认不需要更新组件时使用。
    这些方法的详细说明,可参考官方文档。以下给出一个简单的栗子:
// 每隔100ms重新设置组件的透明度并重新渲染
var Hello = React.createClass({
    getInitialState: function() {
        return {
            opacity: 1.0
        };
    },
    componentDidMount: function() {
        this.timer = setInterval(function(){
            var opacity = this.state.opacity;
            opacity -= .05;
            if(opacity < 0.1) {
                opacity = 1.0;
            }
            this.setState({
                opacity: opacity
            });
        }.bind(this), 100);
    },
    render: function() {
        return (
            <div style={{opacity: this.state.opacity}}>
                Hello {this.props.name}
            </div>
        );
    }
});
ReactDOM.render(
    <Hello name="world"/>,
    document.getElementById('example');
);

注意style={{opacity: this.state.opacity}},React组件样式是一个对象,所以第一重括号表示是JavaScript语法,第二重括号表示样式对象。
以下栗子初始化state,setNewNumber用于更新state。所有生命周期在Content组件中。

var Button = React.createClass({
    getInitialState: function(){
        return {
            data: 0
        };
    },
    setNewNumber: function(){
        this.setState({data: this.state.data+1})
    },
    render: function() {
        return (
            <div>
                <button onClick={this.setNewNumber}>INCREMENT</button>
                <Content myNumber={this.state.data}></Content>
            </div>
        );
    }
});
var Content = React.createClass({
    componentWillMount: function()  {
        console.log('Component will mount!');
    },
    componentDidMount: function() {
        console.log('Component did mount!');
    },
    componentWillReceiveProps: function(newProps) {
        console.log('Component will receive props!');
    },
    shouldComponentUpdate: function(newProps, nextState) {
        return true;
    },
    componentWillUpdate: function(nextProps, nextState) {
        console.log('Component will update!');
    },
    componentDidUpdate: function(prevProps, prevState) {
        console.log('Component did update!');
    },
    componentWillUnmount: function() {
        console.log('Component will unmount!');
    },
    render: function() {
        return (
            <div>
                <h3>{this.props.myNumber}</h3>
            </div>
        );
    }
});

五、React AJAX

React组件的数据可以通过componentDidMount方法中的AJAX来获取,当从服务端获取数据库可以将数据存储在state中,再用this.setState方法重新渲染UI。
当使用异步加载数据时,在组件卸载前使用componentWillUnmount来取消未完成的请求。
以下栗子演示了获取Github用户最新gist共享描述:

var UserGist = React.createClass({
    getInitialState: function() {
        return {
            username: '',
            lastGistUrl: ''
        };
    },
    componentDidMount: function() {
        this.serverRequest = $.get(this.props.source, function(result) {
            var lastGist = result[0];
            this.setState({
                username: lastGist.owner.login,
                lastGistUrl: lastGist.html_url
                });
            }.bind(this));
    },
    componentWillUnmount: function() {
        this.serverRequest.abort();
    },
    render: function() {
        return (
            <div>
                {this.state.username} 用户最新的Gist共享地址:
                <a href={this.state.lastGistUrl}>{this.state.lastGistUrl}</a>
            </div>
        );
    }
});
ReactDOM.render(
    <UserGist source="https://api.github.com/users/octocat/gists" />,
    document.getElementById('example')
);

上述代码使用jQuery来完成AJAX请求,这是为了便于说明。React本身没有任何依赖,完全可以不用jQuery,而使用其他库,我们也可以通过把一个Promise对象传入组件,如下所示:

var RepoList = React.createClass({
    getInitialState: function() {
        return {
            loading: true,
            error: null,
            data: null
        };
    },
    componentDidMount() {
        this.props.promise.then(
            value => this.setState({loading: false, data: value}),
            error => this.setState({loading: false, error: error}));
    },
    render: function() {
        if(this.state.loading) {
            return <span>Loading...</span>;
        }
        else if(this.state.error !== null) {
            return <span>Error: {this.state.error.message}</span>;
        }
        else {
            var repos = this.state.data.items;
            var repoList = repos.map(function(repo, index){
                return (
                    <li key={index}>
                        <a href={repo.html_url}>{repo.name}</a>
                        ({repo.stargazers_count} stars)
                        <br/> {repo.description}
                    </li>
                );
            });
            return (
                <main>
                    <h1>Most Popular JavaScript Projects in Github</h1>
                    <ol>{repoList}</ol>
                </main>
            );
        }
    }
});
ReactDOM.render(
    <RepoList promise={$.getJSON('https://api.github.com/search/repositories?q=javascript&sort=stars')} />,
    document.getElementById('example')
);

上述代码从github的API抓取数据,然后Promise对象作为属性,传给RepoList组件。如果Promise对象正在抓取数据(pending状态),组件显示“正在加载”;如果Promise对象报错(rejected状态),组件显示报错信息;如果Promise对象抓取数据成功(fullfill状态),组件显示获取的数据。

六、参考

[1] React教程
[2] React 入门实例教程

猜你喜欢

转载自blog.csdn.net/Jingle_cjy/article/details/78797101