React學習筆記2:React官方CommentBox實踐

StonePanda發表於2016-01-22

新搭建的個人部落格,本文地址:React學習筆記2:React官方CommentBox實踐
所有的操作是繼續上一個學習筆記,參考的是React官方的CommentBox,不過不是100%按照其實現。
參考:https://facebook.github.io/re…

1、首先建立相關的檔案

touch src/comment.js

2、修改webpack配置,一處是告訴webpack預處理的實體增加comment.js,另外一個是告訴webpack輸出的時候按照檔名字編譯輸出,而不是將所有js檔案編譯到bundle.js,[name]實際上是entry陣列中的key,通過修改key可以歸類目錄,例如`comment/index`:./src/index.js,`comment/index`:`./src/comment.js`會將編譯後的檔案放到comment目錄下,通過這些配置可以更好的組織程式碼結構

  entry:{
    `index`:`./src/index.js`,
    `comment`:`./src/comment.js`
  },
  output: {
     path: path.resolve(__dirname, `build`),
     filename: `[name].js`
  },

3、修改`build/index.html`引入檔案修改為comment.js,重新執行webpack-dev-server,開始修改comment.js

<script src="comment.js"></script>

4、分拆Comment元件,梳理出如下結構,在React中所有的東西都是以元件的形式存在

- CommentBox
  - CommentList
    - Comment
  - CommentForm

5、建立CommentBox元件,return()中的類html內容,在react中叫做JSX語法,其本身符合XML語法,react在編譯後會轉化為相應的js檔案,官方介紹https://facebook.github.io/re…
之後可以去瀏覽器中看下效果

var CommentBox = React.createClass({
  render:function(){
    return (
      <div className=`commentBox`>
          Hello world! I am a comment box.
      </div>
    )
  }
});
//渲染元件,注意修改index.html中div的id為content
ReactDOM.render(
  <CommentBox />,document.getElementById(`content`)
);

6、建立CommentList、CommentForm元件

var CommentList = React.createClass({
  render:function(){
    return (
      <div className=`commentList`>
        Hello, I am a comment list!
      </div>
    )
  }
});

var CommentForm = React.createClass({
  render:function(){
    return (
      <div className=`commentForm`>
        hi, I am a comment form.
      </div>
    )
  }
});

7、修改CommentBox程式碼,引入CommentList、CommentForm元件。下面程式碼中混合了HTML和元件程式碼,JSX編譯器會自動將HTML程式碼用React.createElement(tagName)去轉換。瀏覽器看下效果。

var CommentBox = React.createClass({
  render:function(){
    return (
      <div className=`commentBox`>
        <h1>Comments</h1>
        <CommentList />
        <CommentForm />
      </div>
    )
  }
});

8、建立Comment元件,裡面有{this.props.author}和{this.props.children}兩個變數,稱之為元件的屬性。修改CommentList元件,可以看到我們傳遞了author屬性。children是React預置屬性,指向元件內嵌的內容。返回瀏覽器檢視修改變化。

var Comment = React.createClass({
  render:function(){
    return (
      <div className=`comment`>
        <h2 className=`commentAuthor`>
          {this.props.author}
        </h2>
        {this.props.children}
      </div>

    )
  }
})
//修改CommentList元件,讓其載入Comment元件
var CommentList = React.createClass({
  render:function(){
    return (
      <div className=`commentList`>
        <Comment author=`stone`>就是瞎比比</Comment>
        <Comment author=`mpanda`>我不聽瞎比比</Comment>
      </div>
    )
  }
});

9、新增Markdown支援

//安裝依賴包
npm install marked --save
//引入marked包
var marked = require(`marked`)
//修改Comment元件,利用marked元件解析評論內容,轉為富文字格式。使用toString()是為了明確傳送給marked的為字串格式。瀏覽器看效果效果
var Comment = React.createClass({
  render:function(){
    return (
      <div className=`comment`>
        <h2 className=`commentAuthor`>
          {this.props.author}
        </h2>
        {marked(this.props.children.toString())}
      </div>

    )
  }
})
//顯示效果
Comments
stone
<p>就是瞎比比</p>
mpanda
<p>我不聽瞎比比</p>
hi, I am a comment form.

10、解析後的文字直接被顯示在頁面上,並沒有被瀏覽器解析,這是react為了防止被XSS攻擊而作的保護措施。React提供了一個並不友好的特殊API保證能夠實現在瀏覽器顯示原始HTML

var Comment = React.createClass({
  rawMarkup:function(){
    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>

    )
  }
})

JSX中dangerouslySetInnerHTML屬性必須在接收到一個物件引數,且物件引數中明確使用__html作為key時,才會將其內容作為原始HTML插入頁面中,而且不建議直接在<div dangerouslySetInnerHTML={{__html:marked(this.props.children.toString(),{sanitize:true})}} />直接這樣完成書寫,目的就是明確提醒開發者,這裡是有風險的,您是絕對的信任這段插入的內容。再次檢視瀏覽器效果。It works!
11、評論資料顯然應該來自伺服器,不過在動態獲取之前,我們先模擬一些資料

var data = [
  {"id":1,"author":"stone","text":"換一個位置瞎比比"},
  {"id":2,"author":"mpanda","text":"不喜歡你瞎比比"},
]
//傳遞資料到CommentBox
ReactDOM.render(
  <CommentBox data={data} />,document.getElementById(`content`)
);
//直接傳遞資料到CommentList
var CommentBox = React.createClass({
  render:function(){
    return (
      <div className=`commentBox`>
        <h1>Comments</h1>
        <CommentList data={this.props.data} />
        <CommentForm />
      </div>
    )
  }
});
//在CommentList重新完成元件的組裝,重新整理瀏覽器看效果
var CommentList = React.createClass({
  render:function(){
    var commentNodes = this.props.data.map(function(comment){
      return (
        <Comment author={comment.author} key={comment.id}>
          {comment.text}
        </Comment>
      )
    });
    return (
      <div className=`commentList`>
        {commentNodes}
      </div>
    )
  }
});

12、從元件封裝來說我們已經封裝了一個很不錯的元件,只需要傳遞相關json資料到CommentBox即可。不過所有的資料都是在元件建立的時候,利用不可變數引數props一次性傳遞給元件。state同樣為元件的私有變數,可以通過this.setState()來設定變數的值,每次設定變數的值,元件都會重新渲染一遍自己。利用state修改我們的程式,讓其動態渲染頁面。

//為了方便進行ajax請求,引入jquery,當然完全可以不引入
var $ = require("jquery")
var CommentBox = React.createClass({
  //getInitialState函式在元件的整個生命週期只會執行一次,我們在裡面初始化資料
  getInitialState:function(){
    return {data:[]}
  },
  //componentDidMount函式同樣是有React自動呼叫,時間是在元件第一渲染完畢後。當然因為data在初始化的時候資料為空,實際上這時候渲染的元件沒有內容。
  componentDidMount:function(){
    $.ajax({
      url:this.props.url,
      //因我本地server是php,且跨域,所以我們使用jsonp解決跨域問題,具體jsonp實現,請自行google
      dataType:"jsonp",
      cache:false,
      jsonp:`callback`,
      jsonpCallback:`getComment`,
      success:function(data){
        //獲取到資料後,通過setState設定資料,元件會自動再次渲染
        this.setState({"data":data})
      }.bind(this),
      error:function(xhr,status,err){
        console.log(this.props.url,status,err.toString())
      }.bind(this)
    })
  },
  render:function(){
    return (
      <div className=`commentBox`>
        <h1>Comments</h1>
        <CommentList data={this.state.data} />
        <CommentForm />
      </div>
    )
  }
});
//將URL地址傳遞個元件
ReactDOM.render(
  <CommentBox url="http://***.local.com/" />,document.getElementById(`content`)
);

13、我們在瀏覽器看效果的時候因為資料請求都是在毫秒級別完成不方便看到重新渲染的效果,我們引入一個定時器。重新整理瀏覽器,哈哈,2s後自動載入了評論資料。

var CommentBox = React.createClass({
  getInitialState:function(){
    return {data:[]}
  },
  loadCommentsFromServer:function(){
    $.ajax({
      url:this.props.url,
      dataType:"jsonp",
      cache:false,
      jsonp:`callback`,
      jsonpCallback:`getComment`,
      success:function(data){
        this.setState({"data":data})
      }.bind(this),
      error:function(xhr,status,err){
        console.log(this.props.url,status,err.toString())
      }.bind(this)
    })
  },
  componentDidMount:function(){
    setInterval(this.loadCommentsFromServer,2000)
  },
  render:function(){
    return (
      <div className=`commentBox`>
        <h1>Comments</h1>
        <CommentList data={this.state.data} />
        <CommentForm />
      </div>
    )
  }
});

14、官方教程中評論提交分兩種完成。第一種完成了提交完成後,需要重新整理,再次從伺服器獲取評論資料,第二種提交評論後直接把提交的資料附加到評論後面,利用setState重新渲染頁面。顯然第二種體驗更好,直接實現第二種。
先做分析,在CommentForm元件中,如果完成資料的提交,那麼需要重新設定CommentList中的資料,但是CommentList的資料又是CommentBox傳遞過去的,那麼提交資料的操作不如直接在CommentBox中完成,然後利用setState重新設定CommentList的資料,CommentList完成自動重新整理。

//之前獲取評論介面利用的是jsonp,但是提交評論必須post方法,所以jsonp無法完成,但是有不能通過跨域操作。webpack支援proxy(代理)模式,可以把一部分介面直接轉發到後端,修改webpack配置,請自行替換後端服務。
    devServer:{
        contentBase:`./build`,
        proxy:{
            "/api/*":{
                target:"http://***.local.com:80",
                host:"***.local.com",
                secure: false,
            },
            bypass: function(req, res, proxyOptions) {
                if (req.headers.accept.indexOf(`html`) !== -1) {
                    console.log(`Skipping proxy for browser request.`);
                    return `/index.html`;
                }
            },
        }
    }
//注意傳遞的url,會自動轉發到http://***.local.com:80/api/comment
ReactDOM.render(
  <CommentBox url="http://localhost:8080/api/comment" />,document.getElementById(`content`)
);
var CommentBox = React.createClass({
  //增加評論提交方法,後臺服務  
  handleSubmitComment:function(data){
    $.ajax({
      //請注意,後臺介面我把評論和獲取評論放到了一起,只是提交方式不一樣,一個是get,一個是post
      url:this.props.url,
      type:"POST",
      data:data,
      dataType:"json",
      cache:false,
      success:function(data){
        //測試介面直接返回了我提交的內容,所以可以直接附加資料,讓CommentList自動重新整理
        this.setState({data:this.state.data.concat(data)});
      }.bind(this),
      error:function(xhr,status,err){
        console.log(this.props.url,status,err.toString())
      }.bind(this)
    })
  },
  render:function(){
    return (
      <div className=`commentBox`>
        <h1>Comments</h1>
        <CommentList data={this.state.data} />
        //將評論提交介面傳遞個CommentForm元件
        <CommentForm onSubmitComment={this.handleSubmitComment} />
      </div>
    )
  }
});

var CommentForm = React.createClass({
  getInitialState:function() {
    return {author:"",text:""}
  },
  //完成資料的繫結,通過setState也能保證跟此資料相關的UI完成重新的渲染
  handleAuthorChange:function(event){
    this.setState({author:event.target.value})
  },
  handleTextChange:function(event){
    this.setState({text:event.target.value})
  },
  handleSubmit:function(event){
    //組織表單預設的submit提交
    event.preventDefault();
    var author = this.state.author.trim()
    var text = this.state.text.trim()
    if(!text||!author) {
      return;
    }
    //呼叫CommentBox上的評論提交方法
    this.props.onSubmitComment({author:author,text:text});
    this.setState({author:"",text:""})
  },

  render:function(){
    return (
      <form className=`commentForm` onSubmit={this.handleSubmit}>
        <input type=`text` onChange={this.handleAuthorChange}
           placeholder=`怎麼稱呼您呢?爺` value={this.state.author} />
         <input type=`text` onChange={this.handleTextChange}
           placeholder=`爺有什麼賜教?` value={this.state.text} />
         <input type=`submit` value=`提交` />
      </form>
    )
  }
});

到此整個示例聯絡完成!之後要完成用es6語法重構該專案。

相關文章