他們是真的親戚,可不像 Java 和 Javascript 同樣。html
其實第一次看到 React 的語法我是拒絕的,由於這麼醜的寫法,你不能讓我寫我就寫。前端
但當我發現 React Native 橫空出世後,它學習一次處處運行的理念很是誘人。React Native 能夠寫出原生體驗的 iOS/Android 應用?那不就多了一門裝逼技能?因此咱們調研小組試了一下,感受 "Duang" 一下,很爽很舒服。寫 React Native 須要兩門基礎技能:React 語法 和 iOS 基礎知識。node
很爽很舒服,索性就研究一下,算是入門。
瞭解以後發現,React 真是有另外一番天地,值得學習。react
接下來總結如下我對 React 的理解,分享給你們。webpack
至於 React Native,有機會再好好探究下。git
這部分廢話太多,喜歡實戰的能夠直接看代碼部分。es6
React 是 Facebook 出品的一套顛覆式的前端開發類庫。github
爲何說它是顛覆式的呢?web
對於傳統的 DOM 維護,咱們的步驟多是:
首先,咱們操做 DOM 是最昂貴的開銷,對於 須要反覆更新 DOM 的網頁,無疑是噩夢。其次,對 DOM 局部的更新以及事件綁定加大了維護的難度。
而 React 引入了一個全新的概念:虛擬 DOM。
虛擬 DOM 是躺在內存裏的一種特殊的結構,咱們能夠理解爲這是真實 DOM 在內存裏的映射。
除告終構上的映射外,這個虛擬的 DOM 還包括了渲染
真實所須要的數據以及事件綁定。
虛擬 DOM 在建立時,首先是使用 JSX 的語法生成一個真實 DOM 樹的映射,其次是從服務器端拉取遠程數據,接着注入到這個虛擬 DOM 樹中,同時綁定事件。
好了,有了虛擬 DOM、數據、事件,萬事俱備。
接下來,調用 render() 方法一次性渲染出真實的 DOM,而後全量插入到網頁中。
虛擬 DOM 靜靜地躺在內存裏,等待數據更新。
新數據來臨,調用 setState() 方法更新數據到虛擬 DOM 中,而後自動調用 render() 再一次性渲染出真實的 DOM ,而後全量更新到網頁中。
一個虛擬 DOM,對應一個真實 DOM 一份數據更新,從新生成虛擬 DOM ,全量更新真實 DOM
就這麼簡單。
除了帶來性能上的提高以外,很顯然這種寫法簡化了咱們維護 DOM 的成本 -- 咱們只須要維護一份數據。
能夠看到,React 裏最重要的概念有虛擬 DOM、單向數據注入(虛擬 DOM 到真實 DOM)。
這裏並無引入太多其餘的東西,因此我對 React 的理解是一個類庫,而非框架。
若是要使用 MVC、MVVM 等技術的吧,徹底能夠把 React 當作其中的 V,即 View, 配合其餘類庫使用。
我雖然是個前端菜鳥,但日觀天象也是能嗅到下一代 Web 將是組件化、組件複用共享的時代。
React 編寫起來,就是編寫一個個的組件。
我對一個 React 組件的理解是:
以上三者能夠打包複用,甚至是無縫接入,我腳得它就多是將來了。
HTML 與 JS 使用 JSX 語法糅合到一塊兒的方式是見仁見智,恐怕會引發戰爭。
我剛接觸到 JSX 的時候,一開口也是『我*,好醜』。
但慢慢地卻發現,這種方式一開始寫起來彆扭,但用得卻很爽。
接下來,我經過編寫一個簡單的應用來入門 React。
看完若是大呼不過癮,建議直飛 React 官方看文檔,那纔是寶藏!
示例代碼放置在 demo/目錄下,每一個文件夾爲一個獨立的示例。
先看下這個 demo 最終的樣子吧:
每一個示例的入口文件 index.html 結構大致相同:
<!-- React 真實 DOM 將會插入到這裏 --> <div id="demo"></div> <!-- 引入 React --> <script src="../../bower_components/react/react.js"></script> <!-- 引入 JSX 語法格式轉換器 --> <script src="../../bower_components/react/JSXTransformer.js"></script> <!-- 注意:script 須要註明 type 爲 text/jsx 以指定這是一個 JSX 語法格式 --> <script type="text/jsx" src="demo.js"></script> </body>
使用 render() 方法生成真實 DOM 並插入到網頁中。
// 使用 React.createClass 建立一個組件 var DemoComponent = React.createClass({ // 使用 render 方法自動渲染 DOM render: function () { return ( <div className="component-hello"> <h1 className="hello-title">Hello React</h1> <p className="hello-desc">React 初探</p> <div className="hello-movies"> <p2>我喜歡的電影</p2> <ul> <li className="movie-item"> <span className="movie-name">速度與激情7</span> - <span className="movie-date">2015</span> </li> </ul> </div> </div> ) } }); // 將組件插入到網頁中指定的位置 React.render(<DemoComponent />, document.getElementById('demo'));
第一次渲染真實 DOM 時將使用 getInitialState() 返回的數據。
// 使用 React.createClass 建立一個組件 var DemoComponent = React.createClass({ // getInitialState 中返回的值將會做爲數據的默認值 getInitialState: function () { return { title: '我喜歡的電影', movies: [ { id: 7, name: '速度與激情7', date: 2015 }, { id: 6, name: '速度與激情6', date: 2013 } ] } }, // 使用 render 方法自動渲染 DOM render: function () { // this.state 用於存儲數據 var title = this.state.title; var movies = this.state.movies.map(function (movie) { return ( <li className="movie-item" key={movie.id}> <span className="movie-name">{movie.name}</span> - <span className="movie-date">{movie.date}</span> </li> ) }); return ( <div className="component-hello"> <h1 className="hello-title">Hello React</h1> <p className="hello-desc">React 初探</p> <div className="hello-movies"> <p2>{title}</p2> <ul>{movies}</ul> </div> </div> ) } }); // 將組件插入到網頁中指定的位置 React.render(<DemoComponent />, document.getElementById('demo'));
第二次更新渲染真實 DOM 時將使用 setState() 設置的數據。
// 使用 componentDidMount 在組件初始化後執行一些操做 componentDidMount: function () { // 拉取遠程數據 // 開啓假數據服務器: // cd fake-server && npm install && node index.js this.fetchData(); }, // 使用自定義的 fetchData 方法從遠程服務器獲取數據 fetchData: function () { var self = this; // 發起 ajax 獲取到數據後調用 setState 方法更新組件的數據 var url = '../../fake-data/movies.json'; $.getJSON(url, function (movies) { // 本地模擬返回太快了,模擬一下網絡延遲 setTimeout(function() { self.setState({ movies: movies }); }, 2000); }); },
綁定事件時,咱們可使用 ref="name" 屬性對一個 DOM 節點進行標記,同時能夠經過 React.findDOMNode(this.refs.name) 獲取到這個節點的原生 DOM。
// 使用 render 方法自動渲染 DOM render: function () { var self = this; // this.state 用於存儲數據 var title = this.state.title; var movies = this.state.movies.map(function (movie) { return ( <li className="movie-item" key={movie.id}> <span className="movie-name">{movie.name}</span> - <span className="movie-date">{movie.date}</span> <a href="#" onClick={self.onRemove.bind(null, movie)}>刪除</a> </li> ) }.bind(this));// 注意這裏 bind(this) 修正了上下文 return ( <div className="component-hello"> <h1 className="hello-title">Hello React</h1> <p className="hello-desc">React 初探</p> <div className="hello-movies"> <p2>{title}</p2> <form onSubmit={this.onAdd}> {/* 注意這裏指定 ref 屬性,而後咱們就可使用 this.refs.xxx 訪問到 */} <input type="text" ref="name" placehlder="輸入你喜歡的電影"/> <input type="text" ref="date" placeholder="上映時間"/> <input type="submit" value="提交"/> </form> <ul>{movies}</ul> {this.state.loading ? <div>你們好我是菊花, 我如今在轉</div> : null} </div> </div> ) }
onRemove: function (movie) { var id = movie.id; console.log(movie) // 刪除這個 item var movies = this.state.movies; var len = movies.length; var index = -1; for(var i = 0; i < len; i++) { var _movie = movies[i]; if (_movie.id === id) { index = i; break; } } if (index > 0) { movies.splice(index, 1); this.setState({ movies: movies }); } }, onAdd: function (e) { e.preventDefault(); var refs = this.refs; var refName = React.findDOMNode(refs.name); var refDate = React.findDOMNode(refs.date); if (refName.value === '') { alert('請輸入電影名'); return; } else if (refDate === '') { alert('請輸入上映時間'); return; } var movie = { // 使用 findDOMNode 獲取到原生的 DOM 對象 name: refName.value, date: refDate.value, id: Date.now() // 粗暴地以時間數字做爲隨機 id }; var movies = this.state.movies; movies.push(movie); this.setState(movies); refName.value = ''; refDate.value = ''; },
一個組件就包含了 JSX 模板、數據維護、事件綁定的話,代碼量已經夠多了,這時候能夠採用 AMD/CMD 的方式,將組件進行更細粒度的劃分,能夠以文件即組件的方式來編寫,這裏就不上 demo 了。
在 React 中,數據流是單向的,且組件之間能夠嵌套,咱們能夠經過對最頂層組件傳遞屬性方式,向下層組件傳送數據。
嵌套組件間,使用 this.props 屬性向下傳遞數據
獨立組件之間,自行維護數據則須要自行維護一個全局數據存儲,或者使用發佈訂閱地方式通知數據的更新。
全局數據存儲怎麼作呢?能夠理解爲不一樣的組件獲取的數據源一致,在組件的外部維護這個數據集合,或者乾脆直接從服務器端獲取。
有人會說了,這樣很不方便。
但我以爲,既然是一個組件,那就配套有獲取組件所需數據的方式,獨立組件間有很強的數據依賴時,要麼使用上述方式,要麼能夠簡單粗暴,將獨立組件用一個頂層組件包裹起來,轉化爲嵌套組件的關係,便可數據互通。
// 將子組件抽離出來 var LiWrapper = React.createClass({ render: function () { // 使用 this.props 得到傳入組件的數據 var movie = this.props.movie; return ( <li>{/* ... */}</li> ) } }); // 使用 React.createClass 建立一個組件 var DemoComponent = React.createClass({ // 使用 getInitialState 的返回值做爲數據的默認值 getInitialState: function () { // ... }, // 使用 render 方法自動渲染 DOM render: function () { // this.state 用於存儲數據 var movies = this.state.movies.map(function (movie) { return ( <LiWrapper movie={movie}/> ) }.bind(this));// 注意這裏 bind(this) 修正了上下文 return ( <div className="component-hello"> {/* ... */} <div className="hello-movies"> <ul>{movies}</ul> </div> </div> ) } }); // 將組件插入到網頁中指定的位置 // 在使用組件時傳入 movies 數據 var movies = [// ...]; React.render(<DemoComponent movies={movies}/>, document.getElementById('demo'));
ES6 和 gulp 的話就很少介紹啦。
webpack 是一款新生的前端構建工具,兼容 AMD/CMD 等寫法,支持 Browser 和 Node 端共享代碼,在瀏覽器端能夠像寫 Node 同樣方便的進行模塊化的劃分。
在這裏主要用 webpack 的兩個插件:
使用 jsx-loader 這個插件支持 jsx 語法解析
使用 esx-loader 這個插件支持 es6 語法解析
來看下簡單目錄結構:
var React = require('react'); var MovieListComponent = require('./components/movie-list'); var HelloMessageComponent = require('./components/hello'); var movies = [ { id: 5, name: '速度與激情5', date: 2011 }, { id: 4, name: '速度與激情4', date: 2009 } ]; var wording = '保羅'; var MainComponent = React.createClass({ render: function () { return ( <div className="component-hello"> <HelloMessageComponent name={wording}/> <MovieListComponent movies={movies} /> </div> ) } }); React.render(<MainComponent />, document.getElementById('demo'));
var React = require('react'); // 引入子組件 var MovieComponent = require('./movie'); // 使用 React.createClass 建立一個組件 var MovieListComponent = React.createClass({ // 使用 getInitialState 的返回值做爲數據的默認值 getInitialState: function () { return { loading: true, title: '我喜歡的電影', // 注意這裏將 外部傳入的數據賦值給了 this.state movies: [] } }, // 使用 render 方法自動渲染 DOM render: function () { // this.state 用於存儲數據 var title = this.state.title; // this.props 用於從組件外部傳入數據 var movies = this.props.movies; movies = movies.map(function (movie) { return ( <MovieComponent movie={movie}/> ) }.bind(this));// 注意這裏 bind(this) 修正了上下文 return ( <ul>{movies}</ul> ) } }); module.exports = MovieListComponent;
var React = require('react'); class HelloComponent extends React.Component { constructor(props) { super(props); this.state = {wording: '你好呀, '}; } render() { return <div>{this.state.wording} {this.props.name}</div> ; } } module.exports = HelloComponent;
module.exports = { entry: ['./js/main.js'], output: { path: __dirname, filename: 'js/bundle.js' }, module: { loaders: [ { test: /\.js$/, loader: 'es6-loader' }, { test: /\.js$/, loader: 'jsx-loader' } ] } };
var gulp = require('gulp'); var livereload = require('gulp-livereload'); var webpack = require("gulp-webpack"); var webpackConfig = require('./webpack.config'); gulp.task("webpack", function() { return gulp.src('./js/main.js') .pipe(webpack(webpackConfig)) .pipe(gulp.dest('.')); }); gulp.task('watch', function() { livereload.listen(); gulp.watch(['js/**/*.js', '!js/bundle.js'], ['webpack']); }); gulp.task('default', [ 'webpack', 'watch' ]);
<!-- React 真實 DOM 將會插入到這裏 --> <div id="demo"></div> <script src="./js/bundle.js"></script>
在 js/main.js 中引入兩個不一樣的組件,而後在 webpack.config.js 中指定編譯 JSX 和 ES6 的 loader 工具,使用 gulp 監聽 js/ 中文件變化,自動編譯出的 js/bundle.js 將被 index.html 引用。
嗯,再在 webpack 中加入各類你喜歡的 loader,在 gulp 中加上各類 css、js、img 的處理任務,編寫代碼,自動從新編譯,縱享絲滑。
文章到這裏應該就算結束了,接下來是一些在學習過程當中記下來的幾個小點,也分享給你們。
JSX 把 JS 和 HTML 糅合起來了,這麼理解是否是感受比較簡單:
遇到 {} 包裹的是 JS,遇到 <> 包裹的是 HTML
好比:
// 錯誤的寫法 var MyComponent = React.createClass({ render: function () { return ( <h1>速度與激情7</h1> <p>致敬保羅</p> ) } });
應該寫成:
// 正確的寫法 var MyComponent = React.createClass({ render: function () { return ( <div> <h1>速度與激情7</h1> <p>致敬保羅</p> </div> ) } });
render()
返回的是一系列嵌套的組件this.props
獲取父組件傳遞給子組件的數據this.setState({data: data});
用於動態更新狀態,設置數據(設置後UI會自動刷新)getInitialState()
在整個組件的生命週期中只會執行一次,用於初始化數據componentDidMount
會在 render 後自動調用,用於異步獲取數據,更新數據gitInitialState()
初始化數據render()
渲染初始化數據componentDidMount()
異步獲取數據setState()
更新數據每個組件均可以理解爲有一個簡單的狀態機。
調用 setState(data, callback) 後,data 將會混入 this.state 中,數據獲得了更新,render() 就會被調用,UI 就能被更新。
<Parent><Child /></Parent>
父組件能夠獲取到子組件:this.props.children
render() 在 React 建立時會調用一次,在數據更新時調用 setState() 方法則會繼續調用它來更新網頁中的真實 DOM。
這個方法返回的值會在組件初始化第一次調用 render() 時就被使用
// 錯誤的寫法 var MyComponent = React.createClass({ render: function () { return ( <div class="movie"> <h1>速度與激情7</h1> <p>致敬保羅</p> </div> ) } });
應該寫成:
// 正確的寫法 var MyComponent = React.createClass({ render: function () { return ( <div className="movie"> <h1>速度與激情7</h1> <p>致敬保羅</p> </div> ) } });
// 錯誤的寫法 var myComponent = React.createClass({ render: function () { return ( <div class="movie"> <h1>速度與激情7</h1> <p>致敬保羅</p> </div> ) } }); React.render(<myComponent />, document.getElementById('demo'));
應該寫成:
// 正確的寫法 var MyComponent = React.createClass({ render: function () { return ( <div className="movie"> <h1>速度與激情7</h1> <p>致敬保羅</p> </div> ) } }); React.render(<MyComponent />, document.getElementById('demo'));
var MyComponent = React.createClass({ getInitialState: function () { return { loading: true } }, showLoading: function () { this.setState({loading: true}) }, hideLoading: function () { this.setState({loading: false}) }, render: function () { return ( { this.state.loading ? <div>你們好我是菊花,我在轉</div> : null } ) } });
React 會爲咱們過濾 XSS,要讓一段 HTML 片斷直接顯示出來,須要這樣:
<div dangerouslySetInnerHTML={{__html: 'First · Second'}} />
React.initializeTouchEvents(true);
表單由於會因用戶交互而變化,因此有特定的一些屬性
input[type=checkbox]
和 input[type=radio]
具備 checked<select multiple={true} value={['B', 'C']}>
表單項具備 onChange 事件
注意若是這麼寫:
render: function() { return <input type="text" value="Hello!" />; }
那每次 render 的時候 input 的 value 都會被重置爲 "Hello!",因此須要這麼控制:
getInitialState: function() { return {value: 'Hello!'}; }, handleChange: function(event) { this.setState({value: event.target.value}); }, render: function() { var value = this.state.value; return <input type="text" value={value} onChange={this.handleChange} />; }
利用這點,能夠無縫地接入一些驗證規則,好比限制文字爲 140 字:
handleChange: function(event) { this.setState({value: event.target.value.substr(0, 140)}); }
若是不想這麼被控制呢?那就在返回 input 的時候,不要設置 value 屬性,這樣隨着用戶輸入,value 不會被重置:
render: function() { return <input type="text" />; }
也能夠設置默認值:
render: function() { return <input type="text" defaultValue="Hello!" />; }
除了 defaultValue
以外,還支持 defaultChecked
React 會在內存裏維護一個表明 DOM 的結構,調用
render 方法時才生成真正的 DOM 插入到網頁中。
一個組件的聲明週期能夠理解爲三個階段:
getInitialState()
被調用,返回原始數據componentWillMount()
在組件 mounting 前調用componentDidMount()
在組件 mounting 完成後調用componentWillReceiveProps(nextProps)
在接收到新的 props 時調用shouldComponentUpdate(nextProps, nextState)
在組件須要更新 DOM 時調用,若這個函數返回 false 則告訴 React 不要更新componentWillUpdate(nextProps, nextState)
在更新發生時調用,能夠在這裏調用 this.steState() 刷新數據componentDidUpdate(prevProps, prevState)
在更新完成後調用forceUpdate() 強制使用數據更新組件,而不用調用 this.setState()
React.findDOMNode(component)
返回原生的 DOM 元素
注意要獲取原生的 DOM 元素,必須在 render 被調用, 真正的 DOM 已經被插入到頁面中時。
能夠把 refs 理解爲咱們在 HTML 中的id,用於定位到指定的組件。
<form onSubmit={this.onAdd}> {/* 注意這裏指定 ref 屬性,而後咱們就可使用 this.refs.xxx 訪問到 */} <input type="text" ref="name" placehlder="輸入你喜歡的電影"/> <input type="text" ref="date" placeholder="上映時間"/> <input type="submit" value="提交"/> </form>
ref 屬性能夠是一個回調函數而不是名字,這個回調會在組件 mounted 後被調用。回調函數使用被引用的組件做爲參數。
<input ref={ function(component){ React.findDOMNode(component).focus();} } />
注意不要在 render 方法中訪問 refs 屬性。