react學習筆記

React 起源於 Facebook 的內部項目,由於該公司對市場上全部 JavaScript MVC 框架,都不滿意,就決定本身寫一套,用來架設 Instagram 的網站。作出來之後,發現這套東西很好用,就在2013年5月開源javascript

這個項目自己也越滾越大,從最先的UI引擎變成了一整套先後端通吃的 Web App 解決方案。衍生的 React Native 項目,目標更是宏偉,但願用寫 Web App 的方式去寫 Native App。若是可以實現,整個互聯網行業都會被顛覆,由於同一組人只須要寫一次 UI ,就能同時運行在服務器、瀏覽器和手機(參見《也許,DOM 不是答案》)。html

須要說明的是,React 能夠在瀏覽器運行,也能夠在服務器運行,可是本教程只涉及瀏覽器。一方面是爲了儘可能保持簡單,另外一方面 React 的語法是一致的,服務器的用法與瀏覽器差異不大。java

1、HTML 模板

使用 React 的網頁源碼,結構大體以下。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">
      // ** Our code goes here! **
    </script>
  </body>
</html>

  

上面代碼有兩個地方須要注意。首先,最後一個 <script> 標籤的 type 屬性爲 text/babel 。這是由於 React 獨有的 JSX 語法,跟 JavaScript 不兼容。凡是使用 JSX 的地方,都要加上 type="text/babel" 。git

其次,上面代碼一共用了三個庫: react.js 、react-dom.js 和 Browser.js ,它們必須首先加載。其中,react.js 是 React 的核心庫,react-dom.js 是提供與 DOM 相關的功能,Browser.js 的做用是將 JSX 語法轉爲 JavaScript 語法,這一步很消耗時間,實際上線的時候,應該將它放到服務器完成。github

2、ReactDOM.render()

ReactDOM.render 是 React 的最基本方法,用於將模板轉爲 HTML 語言,並插入指定的 DOM 節點。算法

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

  

3、JSX(Javascript XML syntax transform) 語法

HTML 語言直接寫在 JavaScript 語言之中,不加任何引號,這就是 JSX語法。JSX 是 Javascript 代碼裏直接寫XML的語法。
後端

var names = ['Alice', 'Emily', 'Kate'];

  ReactDOM.render(
    <div style={{color: '#f00', height:'40px', lineHeight: '40px'}}>
      {
        names.map(function (name) {
          return <div>Hello, {name}!</div>
        })
      }
    </div>,
    document.getElementById('example')
  );

  上面代碼體現了 JSX 的基本語法規則:遇到 HTML 標籤(以 < 開頭),就用 HTML 規則解析;遇到代碼塊(以 { 開頭),就用 JavaScript 規則解析。api

  JSX 容許直接在模板插入 JavaScript 變量。若是這個變量是一個數組,則會展開這個數組的全部成員數組

 

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

  上面代碼的arr變量是一個數組,結果 JSX 會把它的全部成員,添加到模板,運行結果以下。

4、組件

React 容許將代碼封裝成組件(component),而後像插入普通 HTML 標籤同樣,在網頁中插入這個組件。React.createClass 方法就用於生成一個組件類.

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

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

  上面代碼中,變量 HelloMessage 就是一個組件類。模板插入 <HelloMessage /> 時,會自動生成 HelloMessage 的一個實例(下文的"組件"都指組件類的實例)。全部組件類都必須有本身的 render 方法,用於輸出組件。

  注意,組件類的第一個字母必須大寫,不然會報錯,好比HelloMessage不能寫成helloMessage。另外,組件類只能包含一個頂層標籤,不然也會報錯。

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

  上面代碼會報錯,由於HelloMessage組件包含了兩個頂層標籤:h1p

  組件的用法與原生的 HTML 標籤徹底一致,能夠任意加入屬性,好比 <HelloMessage name="John"> ,就是 HelloMessage 組件加入一個 name 屬性,值爲 John。組件的屬性能夠在組件類的 this.props 對象上獲取,好比 name 屬性就能夠經過 this.props.name 讀取。運行結果以下:

  

 

  添加組件屬性,有一個地方須要注意,就是 class 屬性須要寫成 className ,for 屬性須要寫成 htmlFor ,這是由於 class 和 for 是 JavaScript 的保留字。

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

  上面代碼的 NoteList 組件有兩個 span 子節點,它們均可以經過 this.props.children 讀取,運行結果以下。

  

  這裏須要注意, this.props.children 的值有三種可能:若是當前組件沒有子節點,它就是 undefined ;若是有一個子節點,數據類型是 object ;若是有多個子節點,數據類型就是 array 。因此,處理 this.props.children 的時候要當心。

  React 提供一個工具方法 React.Children 來處理 this.props.children 。咱們能夠用 React.Children.map 來遍歷子節點,而不用擔憂 this.props.children 的數據類型是 undefined 仍是 object。更多的 React.Children 的方法,請參考官方文檔

 

6、PropTypes

  組件的屬性能夠接受任意值,字符串、對象、函數等等均可以。有時,咱們須要一種機制,驗證別人使用組件時,提供的參數是否符合要求。

  組件類的PropTypes屬性,就是用來驗證組件實例的屬性是否符合要求.

var MyTitle = React.createClass({
  propTypes: {
    title: React.PropTypes.string.isRequired,
  },

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

  上面的Mytitle組件有一個title屬性。PropTypes 告訴 React,這個 title 屬性是必須的,並且它的值必須是字符串。如今,咱們設置 title 屬性的值是一個數值。

var data = 123;

ReactDOM.render(
  <MyTitle title={data} />,
  document.body
);

  這樣一來,title屬性就通不過驗證了。控制檯會顯示一行錯誤信息。

Warning: Failed propType: Invalid prop `title` of type `number` supplied to `MyTitle`, expected `string`.

  更多的PropTypes設置,能夠查看官方文檔。

此外,getDefaultProps 方法能夠用來設置組件屬性的默認值。

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

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

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

  上面代碼會輸出"Hello World"。

7、獲取真實的DOM節點

 

組件並非真實的 DOM 節點,而是存在於內存之中的一種數據結構,叫作虛擬 DOM (virtual DOM)。只有當它插入文檔之後,纔會變成真實的 DOM 。根據 React 的設計,全部的 DOM 變更,都先在虛擬 DOM 上發生,而後再將實際發生變更的部分,反映在真實 DOM上,這種算法叫作 DOM diff ,它能夠極大提升網頁的性能表現。

可是,有時須要從組件獲取真實 DOM 的節點,這時就要用到 ref 屬性

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

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

  

上面代碼中,組件 MyComponent 的子節點有一個文本輸入框,用於獲取用戶的輸入。這時就必須獲取真實的 DOM 節點,虛擬 DOM 是拿不到用戶輸入的。爲了作到這一點,文本輸入框必須有一個 ref 屬性,而後 this.refs.[refName] 就會返回這個真實的 DOM 節點。

須要注意的是,因爲 this.refs.[refName] 屬性獲取的是真實 DOM ,因此必須等到虛擬 DOM 插入文檔之後,才能使用這個屬性,不然會報錯。上面代碼中,經過爲組件指定 Click 事件的回調函數,確保了只有等到真實 DOM 發生 Click 事件以後,纔會讀取 this.refs.[refName] 屬性。

React 組件支持不少事件,除了 Click 事件之外,還有 KeyDown 、CopyScroll 等,完整的事件清單請查看官方文檔

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')
);

  

上面代碼是一個 LikeButton 組件,它的 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);

  上面代碼中,文本輸入框的值,不能用 this.props.value 讀取,而要定義一個 onChange 事件的回調函數,經過 event.target.value 讀取用戶輸入的值。textarea 元素、select元素、radio元素都屬於這種狀況,更多介紹請參考官方文檔。

10、組件的生命週期

組件的生命週期分紅三個狀態:

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

  

上面代碼在hello組件加載之後,經過 componentDidMount 方法設置一個定時器,每隔100毫秒,就從新設置組件的透明度,從而引起從新渲染。

另外,組件的style屬性的設置方式也值得注意,不能寫成

style="opacity:{this.state.opacity};"

  而要寫成

style={{opacity: this.state.opacity}}

  這是由於 React 組件樣式是一個對象,因此第一重大括號表示這是 JavaScript 語法,第二重大括號表示樣式對象

11、Ajax

組件的數據來源,一般是經過 Ajax 請求從服務器獲取,可使用 componentDidMount 方法設置 Ajax 請求,等到請求成功,再用 this.setState 方法從新渲染 UI (查看 demo11 )。

var UserGist = React.createClass({
  getInitialState: function() {
    return {
      username: '',
      lastGistUrl: ''
    };
  },

  componentDidMount: function() {
    $.get(this.props.source, function(result) {
      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
);

  

上面代碼使用 jQuery 完成 Ajax 請求,這是爲了便於說明。React 自己沒有任何依賴,徹底能夠不用jQuery,而使用其餘庫。

咱們甚至能夠把一個Promise對象傳入組件,

ReactDOM.render(
  <RepoList
    promise={$.getJSON('https://api.github.com/search/repositories?q=javascript&sort=stars')}
  />,
  document.body
);

  

上面代碼從Github的API抓取數據,而後將Promise對象做爲屬性,傳給RepoList組件。

若是Promise對象正在抓取數據(pending狀態),組件顯示"正在加載";若是Promise對象報錯(rejected狀態),組件顯示報錯信息;若是Promise對象抓取數據成功(fulfilled狀態),組件顯示獲取的數據。

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) {
        return (
          <li>
            <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>
      );
    }
  }
});
相關文章
相關標籤/搜索