天天看點

React基礎HTML模闆ReactDOM.render()JSX 文法元件this.props.childrenPropTypes擷取真實的DOM節點this.state表單元件的生命周期Ajax

HTML模闆

React主要是兩個庫:

react.js是核心庫

react-dom.js 是提供與 DOM 相關的功能

還有一個Browser.js 的作用是将 JSX 文法轉為 JavaScript 文法,JSX是可以将HTML直接寫在JS裡的語言,這一步通常不在浏覽器端進行,而是通過babel配合React的babel插件預先轉碼好。

$ babel src --out-dir build
           

不過這裡我們為了友善就直接将其引入在浏覽器端轉碼。

于是一個模闆就像是這個樣子:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>React Test</title>
  </head>
  <body>

    <script src="../../build/react.js"></script>
    <script src="../../build/react-dom.js"></script>
    <script src="../../build/browser.min.js"></script>
    <script type="text/babel">
      //our React code
    </script>
  </body>
</html>
           

ReactDOM.render()

ReactDOM.render 是 React 的最基本方法,用于将模闆轉為 HTML 語言,并插入指定的 DOM 節點。

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

JSX 文法

HTML 語言直接寫在 JavaScript 語言之中,不加任何引号,這就是 JSX 的文法。

到 HTML 标簽(以 < 開頭),就用 HTML 規則解析;遇到代碼塊(以 { 開頭),就用 JavaScript 規則解析。

var names = ['Lion', 'and', 'Rabbit'];
ReactDOM.render(
  <div>
    <div>This is {
      names.reduce((a,b)=>a+' '+b,'')
    }!</div>
  </div>,
  document.getElementById('render2')
);
           

JSX 允許直接在模闆插入 JavaScript 變量。如果這個變量是一個數組,則會展開這個數組的所有成員:

var food = [
  <span>福蘿蔔 </span>,
  <span>牛肉 </span>,
];
ReactDOM.render(
  <div>We like to eat:{food}</div>,
  document.getElementById('render3')
);
           

這裡React會給出一個警告,說Each child in an array or iterator should have a unique “key” prop。

這個警告和React的Dom diff算法相關。給周遊生成的Dom節點添加key就使得React在生成虛拟Dom樹時可以記錄下你的操作,在對這部分節點進行實際DOM更新時,通過這個key可以對比原有的節點和新的節點,進行代價最小的插入和删除而不必更新整個Dom組。

這裡的key最好不是數組的下标這類的資料,而是和本Dom節點相關的Unique字元串。最次的選擇是下标,那麼我們修改一下上面的代碼:

var food = [
  <span key='carrot'>福蘿蔔 </span>,
  <span key='beef'>牛肉 </span>,
];
ReactDOM.render(
  <div>We like to eat:{food}</div>,
  document.getElementById('render3')
);
           

元件

React 允許将代碼封裝成元件(component),然後像插入普通 HTML 标簽一樣,在網頁中插入這個元件。

React.createClass 方法就用于生成一個元件類。

//元件類的第一個字母必須大寫,否則會報錯
      var CookBook = React.createClass({
        render: function() {
          //元件類隻能包含一個頂層标簽,否則也會報錯
          //這裡使用一個div作為頂層标簽
          //class要寫為className,for寫為htmlFor,因為JS關鍵字的原因
          return <div className="CookBookItem">
            <h1>{this.props.name}</h1>
            <div>所需時間:{this.props.time}</div>
            <div>所需食材:{this.props.foods}</div>
          </div>;
        }
      });
      var foods = [
        <span key='tomato'>蕃茄、</span>,
        <span key='beef'>牛肉</span>,
      ];
      ReactDOM.render(
        //元件的參數通過HTML屬性傳進去,在裡面使用this.props.xxx擷取
        <CookBook name="蕃茄牛腩" time="約4個小時" foods={foods}></CookBook>,
        document.getElementById('cookBook1')
      );
           

this.props.children

this.props 對象的屬性與元件的屬性一一對應,但是有一個例外,就是 this.props.children 屬性。它表示元件的所有子節點。

//元件類的第一個字母必須大寫,否則會報錯
var CookBook = React.createClass({
  render: function() {
    //元件類隻能包含一個頂層标簽,否則也會報錯
    //這裡使用一個div作為頂層标簽
    //class要寫為className,for寫為htmlFor,因為JS關鍵字的原因
    return <div className="CookBookItem">
      <h1>{this.props.name}</h1>
      <div>所需時間:{this.props.time}</div>
      <div>所需食材:{this.props.foods}</div>
      <ol>
        {
          React.Children.map(this.props.children, function (child) {
            return <li>{child}</li>;
          })
        }
      </ol>
    </div>;
  }
});
var foods = [
  <span key='tomato'>蕃茄、</span>,
  <span key='beef'>牛肉</span>,
];
var stepsArr = [
  <div key='wash'>洗菜</div>,
  <div key='cutve'>切菜</div>,
  <div key='cutmeet'>切肉</div>,
  <div key='cook'>炖</div>,
];
ReactDOM.render(
  //元件的參數通過HTML屬性傳進去,在裡面使用this.props.xxx擷取
  <CookBook name="蕃茄牛腩" time="約4個小時" foods={foods}>{stepsArr}</CookBook>,
  document.getElementById('cookBook1')
);
           

這裡需要注意, this.props.children 的值有三種可能:如果目前元件沒有子節點,它就是 undefined ;如果有一個子節點,資料類型是 object ;如果有多個子節點,資料類型就是 array 。是以,處理 this.props.children 的時候要小心。

React 提供一個工具方法 React.Children 來處理 this.props.children 。我們可以用 React.Children.map 來周遊子節點,而不用擔心 this.props.children 的資料類型是 undefined 還是 object。

PropTypes

元件的屬性可以接受任意值,字元串、對象、函數等等都可以。有時,我們需要一種機制,驗證别人使用元件時,提供的參數是否符合要求。

元件類的PropTypes屬性,就是用來驗證元件執行個體的屬性是否符合要求。

我們在剛才的例子中加入如下代碼:

//元件類的這個屬性就是用來驗證元件執行個體的屬性是否符合要求
propTypes: {
  //這裡要求name屬性是必須的,且必須為字元串
  name: React.PropTypes.string.isRequired,
},
           

再添加元件:

ReactDOM.render(
  //name是必須的,是以這裡會報錯
  <CookBook></CookBook>,
  document.getElementById('cookBook2')
);
ReactDOM.render(
  //name需要是字元串,是以這裡會報錯
  <CookBook name={123} ></CookBook>,
  document.getElementById('cookBook2')
);
           

此外,getDefaultProps 方法可以用來設定元件屬性的預設值。

//設定預設屬性
getDefaultProps : function () {
  return {
    time : '約1小時'
  };
},
           

擷取真實的DOM節點

元件并不是真實的 DOM 節點,而是存在于記憶體之中的一種資料結構,叫做虛拟 DOM (virtual DOM)。隻有當它插入文檔以後,才會變成真實的 DOM 。根據 React 的設計,所有的 DOM 變動,都先在虛拟 DOM 上發生,然後再将實際發生變動的部分,反映在真實 DOM上,這種算法叫做 DOM diff ,它可以極大提高網頁的性能表現。

但是,有時需要從元件擷取真實 DOM 的節點,這時就要用到 ref 屬性。

var Login = React.createClass({
  //處理點選事件
  //React 元件支援很多事件,除了 Click 事件以外,還有 KeyDown 、Copy、Scroll 等
  handleClick: function() {
    //虛拟Dom是拿不到使用者輸入的,使用this.refs.xxx
    //可以擷取到真實的Dom節點
    alert(this.refs.username.value)
  },
  render: function() {
    return (
      <div>
        <h1>If You Are Our Friend, Sign In Please.</h1>
        <input type="text" ref="username" />
        <input type="password" ref="password" />
        <input type="button" value="Sign In" onClick={this.handleClick} />
      </div>
    );
  },
  //在元件加載完成時調用
  componentDidMount:function(){
    //焦點放在username上
    this.refs.username.focus();
  }
});

ReactDOM.render(
  <Login />,
  document.getElementById('signUp')
);
           

this.state

元件免不了要與使用者互動,React 的一大創新,就是将元件看成是一個狀态機,一開始有一個初始狀态,然後使用者互動,導緻狀态變化,進而觸發重新渲染 UI。

var LikeButton = React.createClass({
  //設定初始狀态,是一個對象
  getInitialState: function() {
    return {liked: false};
  },
  handleClick: function(event) {
    //變更狀态
    //隻要狀态變更,就會調用this.render再次渲染
    //至于是否真的會重新整理Dom節點,就要看React使用Dom diff算法的結果了
    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('like')
);
           

表單

使用者在表單填入的内容,屬于使用者跟元件的互動,是以不能用 this.props讀取,或者你通過剛才的真實Dom節點擷取,或者你使用一些事件:

var ContactUs = React.createClass({
  getInitialState: function() {
    return {value: '[email protected]'};
  },
  handleChange: function(event) {
    //通過事件的target來擷取到一個真實的Dom對象就能擷取到值了
    //本質上和使用ref是一樣的,都是獲得真實的Dom節點
    this.setState({value: event.target.value});
  },
  render: function () {
    var value = this.state.value;
    return (
      <div>
        <h3>Leave Your Email</h3>
        <input type="text" value={value} onChange={this.handleChange} />
        <p>{value}</p>
      </div>
    );
  }
})
ReactDOM.render(<ContactUs/>, document.getElementById('contactUs'));
           

元件的生命周期

元件的生命周期分成三個狀态:

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

React 為每個狀态都提供了兩種處理函數,will 函數在進入狀态之前調用,did 函數在進入狀态之後調用,三種狀态共計五種處理函數。

  • componentWillMount()
  • componentDidMount()
  • componentWillUpdate(object nextProps, object nextState)
  • componentDidUpdate(object prevProps, object prevState)
  • componentWillUnmount()

此外,React 還提供兩種特殊狀态的處理函數。

  • componentWillReceiveProps(object nextProps):已加載元件收到新的參數時調用
  • shouldComponentUpdate(object nextProps, object nextState):元件判斷是否重新渲染時調用
var Timer = React.createClass({
  getInitialState: function () {
    return {
      opacity: ,
      time:,
    };
  },
  //Dom一直在變,于是這個方法總是被調用
  componentDidUpdate(prevProps, prevState) {
    console.log(prevState);
  },
  componentDidMount: function () {
    this.timer = setInterval(function () {
      var opacity = this.state.opacity;
      var time = this.state.time;
      time+=;
      opacity -= ;
      if (opacity < ) {
        opacity = ;
      }
      this.setState({
        opacity: opacity,
        time:time,
      });
    }.bind(this), );
  },

  //每100ms重新渲染一次Dom這在以前是開銷非常大的
  //但是React的先建立虛拟Dom再更改必要的實際Dom使得這個操作變得非常輕量
  //注意這裡的style屬性的設定方式
  //React 元件樣式是一個對象,是以第一重大括号表示這是 JavaScript 文法,第二重大括号表示樣式對象。
  render: function () {
    return (
      <div style={{opacity: this.state.opacity}}>
        You have been here for {this.state.time/}s
      </div>
    );
  }
});

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

Ajax

元件的資料來源,通常是通過 Ajax 請求從伺服器擷取,可以使用 componentDidMount 方法設定 Ajax 請求,等到請求成功,再用 this.setState 方法重新渲染 UI 。

var RepoList = React.createClass({
  //這個元件共有3種狀态
  getInitialState: function() {
    return { loading: true, error: null, data: null};
  },

  componentDidMount() {
    //這個元件會傳進來一個promise對象
    //在這個promise對象準備好之後會傳進元件并觸發這個方法
    //設定完成和報錯的方法
    //這裡在完成或失敗之後改變元件的狀态,重新渲染元件
    this.props.promise.then(
            value => this.setState({loading: false, data: value}),
            error => this.setState({loading: false, error: error}));
  },
  //render方法根據元件的狀态重新渲染元件
  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) {
        return (
                <li>
                  <a href={repo.html_url}>{repo.name}</a> ({repo.stargazers_count} stars) <br/> {repo.description}
                </li>
        );
      });
      return (
              <main>
                <h3>Most Popular JavaScript Projects in Github</h3>
                <ol>{repoList}</ol>
              </main>
      );
    }
  }
});
ReactDOM.render(
        <RepoList
                promise={$.getJSON('https://api.github.com/search/repositories?q=javascript&sort=stars')}
        />,
        document.getElementById('list')
);