reactjs初探

ReactJs是目前比较火的前端框架,作为一个前端开发者,对这一块迟迟没有了解实在说不过去。今次文章的目的在于快速对其做一个使用层面的了解(通俗点讲,就是小白文,好吧,现在也只能写小白文),主要参考阮一峰老师的blog,做一些适于自己的整理和分析。

1. HTML模板

React网页源码的结构

    <!DOCTYPE html>
        <html>
          <head>
            <script src="../build/react.js"></script>
            <script src="../build/react-dom.js"></script>
            <script src="../build/browser.min.js"></script>
          </head>
          <body>
            <div id="example"></div>
            <script type="text/babel">
              ReactDOM.render(
                <h1>Hello, world!</h1>,
                document.getElementById('example')
              );
            </script>
          </body>
        </html>

这段代码需要注意两个地方:
1. 引用文件: 用到了三个库。这三个库必须首先加载。其中,react.js是React的核心库,react-dom.js提供与DOM相关的功能。browser.js的作用是将JSX语法转换为javascript语法。
2. script标签: 最后一个script标签的type属性为 text/babel。这是因为JSX语法跟javascript不兼容。凡是用到JSX的地方,都要加上type=’text/babel’

        关于JSX:
            JSX是一个看起来很像XML的JS语法扩展。
            React建议使用JSX,因为它能定义简洁且我们熟知的包含属性的树状结构语法。

            HTML标签与React组件的对比:
                要渲染HTML标签,只需要在JSX里使用小写字母开头的标签名:
                var myDivElement = <div className="foo" />;
                React.render(myDivElement, document.body);

                要渲染React组件,只需创建一个大写字母开头的本地变量:
                var MyComponent = React.createClass({/*...*/});
                var myElement = <MyComponent someProperty={true} />;
                React.render(myElement, document.body);

                React 的 JSX 里约定分别使用首字母大、小写来区分本地组件的类和 HTML 标签。

            JSX 把类 XML 的语法转成纯粹 JavaScript,XML 元素、属性和子节点被转换成 React.createElement 的参数。

                var Nav;
                // 输入 (JSX):
                var app = <Nav color="blue" />;
                // 输出 (JS):
                var app = React.createElement(Nav, {color:"blue"});

2. ReactDOM.render()

ReactDOM.render()是React的基本方法,用于将模板转换为HTML语言,并插入指定DOM节点。

        ReactDOM.render(
            <h1>Hello, world!</h1>,
            document.getElementById('example')
        );

还是一中的例子,如上语法作用即将h1标签插入example节点。

3. JSX 语法

上面已经接触到,HTML语言直接写在JavaScript语言之中,不加任何引号,这就是JSX的语法。它允许HTML与javascript的混写。
看下面一段代码:

      var names = ['Alice', 'Emily', 'Kate'];
      ReactDOM.render(
        <div>
        {
          names.map(function (name) {
            return <div>Hello, {name}!</div>
          })
        }
        </div>,
        document.getElementById('example')
      );

上面代码体现了JSX的基本语法规则:遇到HTML标签(以<开头),就用HTML规则解析;遇到代码块(以{开头),就用JS规则解析。

JSX允许直接在模板插入JS变量。若这个变量是一个数组,会展开数组所有成员:

        var arr = [
          <h1>Hello world!</h1>,
          <h2>React is awesome</h2>,
        ];
        ReactDOM.render(
          <div>{arr}</div>,
          document.getElementById('example')
        );

4. 组件

React允许将代码封装成组件(component),然后像插入普通html标签一样,在网页中插入这个组件。

React.creatClass方法就用于生成一个组件类。

      var HelloMessage = React.createClass({
        render: function() {
          return <h1>Hello {this.props.name}</h1>;
        }
      });

      ReactDOM.render(
        <HelloMessage name="John" />,
        document.getElementById('example')
      );

上面实例中,变量HelloMessage就是一个组件类。模板插入时,会自动生成HelloMessage一个实例。所有组件类都必须有自己的render方法,用于输出组件。

组件的用法与原生的HTML标签完全一致。可任意添加属性(EX:除了class属性要写成className,for要写成htmlFor,因为class和for是javascript的保留字)

组件的属性可在组件类的this.props对象上获取。

PA:
  1. 组件类的第一个字母必须大写
  2. 组件类只能包含一个顶层标签

              var HelloMessage = React.createClass({
                render: function() {
                  return <h1>Hello {this.props.name}</h1>
                        <p>gogo!hangview</p>;
                }
              });
    

    上面这段代码是会报错的,因为包含两个顶层标签h1和p

5. this.props.children

上面提到组件的属性可在组件类的this.props对象上获取。事实上对象的属性和组件的属性一一对应,但有一个例外,就是this.props.children属性。它表示组件的所有子节点。

          var NotesList = React.createClass({
            render: function() {
              return (
                <ol>{
                    React.Children.map(this.props.children, function (child) {
                      return <li>{child}</li>;
                    })
                  }</ol>
              );
            }
          });

          ReactDOM.render(
            <NotesList>
              <span>hello</span>
              <span>world</span>
            </NotesList>,
            document.body
          );

输出结果:

  1. hello
  2. world

上面代码中NodeList组件有两个span节点,它们都可通过this.props.children读取。但是,当组件没有子节点时,其值是undefined,有一个子节点,是Object,只有多个子节点是才为Array。

React提供一个工具方法:React.Children来处理this.props.childen。可以使用React.Children.map来遍历子节点而不必关注数据类型。

6. PropTypes

组件属性可以接受任意值。但有时在验证别人使用组件时,需要看其提供的参数是否符合要求。

组件的ProtoType属性,就是用来验证组件实例的属性是否符合要求。

      var MyTitle = React.createClass({
        propTypes: {
          title: React.PropTypes.string.isRequired,       //title属性是必需的,而且必须为字符串                },

        render: function() {
          return <h1> {this.props.title} </h1>;
        }
      });

      var data = 123;
      ReactDom.render(
        <MyTitle title={data}>,
        document.body
      );
      //控制台会报错,组件属性类型不对
此外,**getDefaultProps**方法可用来设置组件属性的默认值。

    var MyTitle = React.createClass({
      getDefaultProps : function () {
        return {
          title : 'Hello World'
        };
      },

      render: function() {
         return <h1> {this.props.title} </h1>;
       }
    });

    ReactDOM.render(
      <MyTitle />,              //未设置值,读取默认值
      document.body
    );

7. 获取真实的DOM节点

组件并非真实的DOM节点,它只是存在于内存的一种数据结构,叫做虚拟DOM。只有被插入文档后,才会变成真实的DOM。
在React的设计里,所有的DOM变动,都先在虚拟DOM发生,然后再将实际变动部分反映在真实的DOM上,它可以极大程度地提高网页的性能表现。

当需要从组件获取真实的DOM节点时,要用到ref属性:

    var MyComponent = React.createClass({
      handleClick: function() {
        this.refs.myTextInput.focus();
      },
      render: function(){
        return (
          <div>
            <input type="text" ref="myTextInput" />   //ref属性
            <input type="button" value="Focus the text input" onClick={this.handleClick} />
          </div>           
        );
      }
    });

    ReactDOM.render(
      <MyComponent />,
      document.getElementById('example')
    );

子节点有一个文本输入框,用于获取用户输入。这时需要真正的DOM节点,虚拟DOM是拿不到用户输入的。为了做到这一点,文本框要有一个ref属性(ref=myTextInput),然后this.refs.[refName]就会返回真实的DOM节点。

需要注意的是,因为this.refs.[refName]属性获取的是真实DOM,所以必须等到虚拟DOM插入文档后才能使用这个属性。上面实例中通过为组件指定click的回调函数,确保只有等到真实DOM发生click时间后,才会读取ref属性。

8. this.state

组件要跟用户互动,React的一大创新就是将组件看成一个状态机,一开始有一个初始状态,然后用户互动,导致状态变化,从而触发重新渲染UI。

    var LikeButton = React.createClass({
      getInitialState: function() {
        return {liked: false};
      },
      handleClick: function(event) {
        this.setState({liked: !this.state.liked});
      },
      render: function() {
        var text = this.state.liked ? 'like' : 'haven\'t liked';
        return (
          <p onClick={this.handleClick}>
            You {text} this. Click to toggle.
          </p>
        );
      }
    });

    ReactDOM.render(
      <LikeButton />,
      document.getElementById('example')
    );

上面实例的组件中,getInitialState方法用户定义初始状态,也就是一个对象。这个对象可以通过this.state属性获取。当用户点击组件,导致状态变化,this.setState方法就修改状态值,每次修改会自动调用this.render方法,再次渲染组件。

this.props与this.state的区别:

this.props标示那些一旦定义,就不再改变的属性,而this.state是会随用户互动而产生变化的特性。

9. 表单

用户在表单输入的内容,属于用户跟组件的互动,不能用this.props获取

     var Input = React.createClass({
        getInitialState: function() {
          return {value: 'Hello!'};
        },
        handleChange: function(event) {
          this.setState({value: event.target.value});
        },
        render: function () {
          var value = this.state.value;
          return (
            <div>
              <input type="text" value={value} onChange={this.handleChange} />
              <p>{value}</p>
            </div>
          );
        }
      });

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

上面例子中,文本框输入的值,通过定义一个onChange事件的回调函数,用event.target.value读取用户输入的值
。textarea、select、radio元素都属于这种情况。

10. 组件的生命周期

组件的生命周期主要分为三个状态

React为每个状态提供两种处理函数,will函数在进入状态前调用,did函数在进入状态后调用。

  • Mounting 已插入真实DOM componentWillMount() componentDidMount()
  • Updating 正在被重新渲染 componentWillUpdate() componentDidUpdate()
  • Unmounting 已移除真实DOM componentWillUnmount()

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

  • componentWillReceiveProps(object nextProps) 已加载组件收到新的参数时调用
  • shouldcomponentUpdate(object nextProps,object nextState) 组件判断是否重新渲染时调用

          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.body
          );
    

    上面例子中,在组件加载后,通过componentDidMount方法设置一个定时器,作用是定时更新文字透明度。当状态被更新时,页面也就重新被渲染。

11. Ajax

组件的数据来源,通常是通过Ajax请求从服务器获取。主要实现思路是通过 componentDidMount方法设置Ajax请求,等到请求成功,通过this.setState方法重新渲染UI。

   var UserGist = React.createClass({
      getInitialState: function() {            //状态初始化
        return {
          username: '',
          lastGistUrl: ''
        };
      },

      componentDidMount: function() {
        $.get(this.props.source, function(result) {        //组件加载后,设置Ajax请求
          var lastGist = result[0];
          if (this.isMounted()) {
            this.setState({
              username: lastGist.owner.login,               //更新状态
              lastGistUrl: lastGist.html_url
            });
          }
        }.bind(this));                  //传入当前作用域
      },

      render: function() {
        return (
          <div>
            {this.state.username}'s last gist is <a href={this.state.lastGistUrl}>here</a>.
          </div>
        );
      }
    });

    ReactDOM.render(
      <UserGist source="https://api.github.com/users/octocat/gists" />,
      document.body
    );

12. 代码分析

下面来具体分析一段代码,如下是reactJs官方文档的一段实例代码。通过分析回顾一下前面整合的内容。

    //Comment组件
    var Comment = React.createClass({
      rawMarkup: function() {   //markdown文本格式处理
        var rawMarkup = marked(this.props.children.toString(), {sanitize: true});
        return { __html: rawMarkup };
      },

      render: function() {
        return (
          <div className="comment">              //只有一个顶层标签
            <h2 className="commentAuthor">
              {this.props.author}                
            </h2>
            <span dangerouslySetInnerHTML={this.rawMarkup()} />
          </div>
        );
      }
    });


    //父级组件CommentBox(包含子组件:CommentList和CommentForm)
    var CommentBox = React.createClass({
      loadCommentsFromServer: function() {          //加载数据的Ajax请求
        $.ajax({
          url: this.props.url,
          dataType: 'json',
          cache: false,
          success: function(data) {
            this.setState({data: data});             //更新组件状态,data数据来源
          }.bind(this),
          error: function(xhr, status, err) {
            console.error(this.props.url, status, err.toString());
          }.bind(this)
        });
      },
      handleCommentSubmit: function(comment) {       //Ajax处理新增数据
        var comments = this.state.data;
        // Optimistically set an id on the new comment. It will be replaced by an
        // id generated by the server. In a production application you would likely
        // not use Date.now() for this and would have a more robust system in place.
        comment.id = Date.now();
        var newComments = comments.concat([comment]);      
        this.setState({data: newComments});
        $.ajax({
          url: this.props.url,
          dataType: 'json',
          type: 'POST',
          data: comment,
          success: function(data) {
            this.setState({data: data});
          }.bind(this),
          error: function(xhr, status, err) {
            this.setState({data: comments});
            console.error(this.props.url, status, err.toString());
          }.bind(this)
        });
      },
      getInitialState: function() {               //初始化状态,数据源头
        return {data: []};
      },
      componentDidMount: function() {           //组件加载完后调用的方法
        this.loadCommentsFromServer();            //加载数据
        setInterval(this.loadCommentsFromServer, this.props.pollInterval);  //设置定时器定时刷新
      },
      render: function() {
        return (
          <div className="commentBox">
            <h1>Comments</h1>           
            <CommentList data={this.state.data} />                         //包含两个子组件
            <CommentForm onCommentSubmit={this.handleCommentSubmit} />
          </div>
        );
      }
    });

    //CommentList组件(包含子组件Comment)
    var CommentList = React.createClass({
      render: function() {
        var commentNodes = this.props.data.map(function(comment) {   //从父组件传入的数据(data)会作为子组件的属性,这些属性可以通过this.props访问到。
          return (
            <Comment author={comment.author} key={comment.id}>  //遍历输出每一条数据
              {comment.text}   
              //子节点 ,Comment组件通过this.props.children访问到这些数据
            </Comment>
          );
        });
        return (
          <div className="commentList">
            {commentNodes}
          </div>
        );
      }
    });

    var CommentForm = React.createClass({
      getInitialState: function() {
        return {author: '', text: ''};           //初始化状态
      },
      handleAuthorChange: function(e) {          //绑定事件的回调函数,下同
        this.setState({author: e.target.value});  
      },
      handleTextChange: function(e) {
        this.setState({text: e.target.value});
      },
      handleSubmit: function(e) {
        e.preventDefault();
        var author = this.state.author.trim();
        var text = this.state.text.trim();
        if (!text || !author) {
          return;
        }
        this.props.onCommentSubmit({author: author, text: text});   
        //父组件属性,当事件触发时(submit),调用父组件方法,处理新增评论数据,这样整个父组件状态会被重置,可以重新渲染整个组件相关UI
        this.setState({author: '', text: ''});      //状态初始化
      },
      render: function() {
        return (
          <form className="commentForm" onSubmit={this.handleSubmit}> //绑定事件,下同
            <input
              type="text"
              placeholder="Your name"
              value={this.state.author}
              onChange={this.handleAuthorChange}
            />
            <input
              type="text"
              placeholder="Say something..."
              value={this.state.text}
              onChange={this.handleTextChange}
            />
            <input type="submit" value="Post" />
          </form>
        );
      }
    });

    ReactDOM.render(            //最终render CommentBox这个组件,设置定时加载时间和Ajax url
      <CommentBox url="/api/comments" pollInterval={2000} />,                    
      document.getElementById('content')
    );

上面的这个例子很明显地看出,ReactJs组件化的魅力,它将每一个小模块都抽象成一个个组件,通过state控制组件的渲染,其数据沿着组件树从上到下流动,也可以通过设置触发事件更新上级state达到反向数据流的目的。

后续:

接下来会继续深入梳理reactJs相关内容,更多做一些偏实践的总结。
另外4月份的主要学习计划如下
js前端框架、MVC模块化开发、react的进一步梳理
css基础的回顾和整理,Sass/Less的了解和使用学习
Http协议学习
继续回顾学习JS基础
buffer或工作需求:对yii2和LNMP环境做一些使用层面的了解和总结
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值