【起初的準備工做】css
npm init npm install --save react react-dom npm install --save-dev html-webpack-plugin webpack webpack-dev-server babel-core babel-loader babel-preset-react
【文件結構】html
app/ .....index.html .....index.js .babelrc package.json webpack.config.js
【使用webpack需求與實現】node
npm run 某某名稱
,來執行webpack -p
命令也就是:react
app/ .....index.html .....index.js dist/ .....index.html .....index_bundle.js .babelrc package.json webpack.config.js
webpack.config.js, 這個webpack的配置文件中大體包括:接受源文件、放到目標文件夾、使用babel把jsx文件轉換成js文件、對源文件進行復制,等等。webpack
var HtmlWebpackPlugin = require('html-webpack-plugin'); var HtmlWebpackPluginConfig = new HtmlWebpackPlugin({ template: __dirname + '/app/index.html', filename: 'index.html', inject: 'body' }) module.exports = { entry: [ './app/index.js' ], output: { path: __dirname + '/dist', filename: "index_bundle.js" }, module: { loaders: [ {test: /\.js$/, exclude: /node_modules/, loader: "babel-loader"} ] }, plugins: [ HtmlWebpackPluginConfig ] }
inject:'body'
表示把經webpack生成的index_bundle.js文件被引用到body中.babelrc文件中,babel須要對react的jsx進行轉換,配置以下:ios
{ "presets": [ "react" ] }
最後一點,如何運行npm run 某某名稱
,來執行webpack -p
命令呢?git
須要在package.json中配置github
{ "name": "reactjspracticeswithtyler", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "production": "webpack -p" }, "author": "", "license": "ISC", "dependencies": { "react": "^15.1.0", "react-dom": "^15.1.0" }, "devDependencies": { "babel-core": "^6.9.0", "babel-loader": "^6.2.4", "babel-preset-react": "^6.5.0", "bootstrap": "^3.3.6", "html-webpack-plugin": "^2.17.0", "webpack": "^1.13.1", "webpack-dev-server": "^1.14.1" } }
以上,在scripts下的配置,意思是說運行npm run 某某名稱
,實際是執行webpack -p
命令。web
【第一個React組件】正則表達式
app/index.html
<!doctype html> <html> <head> <meta charset="UTF-8"> <title>Untitled Document</title> <link rel="stylesheet" href="../node_modules/bootstrap/dist/css/bootstrap.min.css"> </head> <body> <div id="app"></div> </body> </html>
以上,咱們即將要把React組件插入到id爲app的div中。
app/index.js,在這裏建立組件
var React = require('react'); var ReactDOM = require('react-dom'); //一、生產Component //這裏是jsx的語法 var HelloWorld = React.createClass({ render: function(){ //console.log(this.props); return ( <div>Hello {this.props.name}</div> ) } }); //二、渲染出來 ReactDOM.render( <HelloWorld name="darren" anySortData={29}/>, document.getElementById('app') );
React.createClass
建立組件, render
必不可少不然報錯,return
返回的語法就是jsx語法,未來須要用Babel
轉換成js文件HelloWord
,第一個字母通常大寫ReactDOM.Render
用來把組件渲染到DOM上<HelloWorld name="darren" anySortData={29}/>
,這裏,對name和anySortData的賦值,實際上會賦值到組件的this.props.name和this.props.anySortData中運行
npm run production
,由於咱們在package.json
中的scripts下已經有了設置,至關於運行webpack -p
命令,接下來會根據webpack.config.js
中的設置,找到app/index.js,使用babel把index.js中的jsx部分轉換成js保存到dist/index_bundle.js中;這時候,html-webpack-plugin
開始工做了,把app/index.html複製保存到dist/index.html中,並把index_bundle.js中注入到dist/index.html的body中。
還有一個問題須要解決,當在瀏覽器中輸入localhost:8080
的時候,能瀏覽到網頁。怎麼作呢?
須要在
package.json
中進行設置,在scripts中進行以下設置
{ "name": "reactjspracticeswithtyler", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "production": "webpack -p", "start":"webpack-dev-server" }, "author": "", "license": "ISC", "dependencies": { "react": "^15.1.0", "react-dom": "^15.1.0" }, "devDependencies": { "babel-core": "^6.9.0", "babel-loader": "^6.2.4", "babel-preset-react": "^6.5.0", "bootstrap": "^3.3.6", "html-webpack-plugin": "^2.17.0", "webpack": "^1.13.1", "webpack-dev-server": "^1.14.1" } }
以上,在scripts下添加了"start":"webpack-dev-server"
部分。
運行
npm run start
命令,在瀏覽器中輸入localhost:8080
就能夠看到內容。
【組件嵌套】
var USER_DATA = { name: 'darren', username: 'DarrenActive', image: 'https://avatars0.githubusercontent.com/u/2933430?v=38s=460' }; var React = require('react'); var ReactDOM = require('react-dom'); //被嵌套組件 var ProfilePic = React.createClass({ render: function(){ return <img src={this.props.imageUrl} style={{height: 100, width: 100}} /> } }) //被嵌套組件 var ProfileLink = React.createClass({ render: function(){ return ( <div> <a href={'https://www.github.com/' + this.props.username}> {this.props.username} </a> </div> ) } }); //被嵌套組件 var ProfileName = React.createClass({ render: function(){ return ( <div>{this.props.name}</div> ) } }) //嵌套組件 var Avatar = React.createClass({ render: function(){ return ( <div> <ProfilePic imageUrl={this.props.user.image} /> <ProfileName name={this.props.user.name} /> <ProfileLink username={this.props.user.username} /> </div> ) } }) ReactDOM.render( <Avatar user={USER_DATA} />, document.getElementById('app') );
嵌套組件和被嵌套組件的關係就像河流的上下游,這裏的嵌套組件Avatar在河流的上游,這裏的被嵌套組件ProfileName在河流的下游,Avatar組件經過user來獲取外界的賦值,user接受到值後往下游的ProfileName流動,user中的值再賦值給ProfileName的name,依次類推。
因此,React的數據流動是單向的,由外向內的流動。
【組件元素嵌套】
組件嵌套和組件元素嵌套不同。組件嵌套大體是:
var Room = React.createClass({ render: function(){ return ( <Table /> <Chair /> <Clock /> ) } });
組件元素嵌套大體是:
var Room = React.createClass({ render: function(){ return ( <Table /> <Chair /> <Clock> <Time /> <Period /> </Clock> ) } });
也就是說,組件元素嵌套中的被嵌套組件,這裏的<Time />
和<Period />
不是被放在一個外層的組件中,而是放在了一個組件元素<Clock></Clock>
。Clock組件的顯示依賴於<Time />
和<Period />
。那麼,在定義Clock組件的時候,如何把<Time />
和<Period />
顯示出來呢?
像這種須要顯示組件元素內的被嵌套組件,就須要this.props.children的幫忙。
像在本項目的Avatar組件的寫法仍是不變:
var Avatar = React.createClass({ render: function(){ return ( <div> <ProfilePic imageUrl={this.props.user.image} /> <ProfileName name={this.props.user.name} /> <ProfileLink username={this.props.user.username} /> </div> ) } })
ProfileLink組件,如今想把它做爲另一個組件元素內的被嵌套組件,這樣定義ProfileLink組件:
var ProfileLink = React.createClass({ render: function(){ return ( <div> <Link href={'https://www.github.com/' + this.props.username}> {this.props.username} </Link> </div> ) } });
以上,{this.props.username}
拿到的值是經過ProfileLink組件組件獲取到的,如何傳遞給Link組件呢?
var Link = React.createClass({ changeURL: function(){ window.location.replace(this.props.href); }, render: function(){ return ( <span style={{color: 'blue', cursor: 'pointer'}} onClick={this.changeURL}> {this.props.children} </span> ) } });
可見,在Link組件中,經過this.props.children
獲取到ProfileLink組件這個組件元素內的組件。值得注意的是:this.props.children
獲取到的是包含在組件元素內的全部被嵌套組件。
【路由】
React的路由鏈接了component和url。
npm install --save react-router@2.0.0-rc5
在app文件夾下建立config文件夾,並建立routes.js文件;在app文件夾下建立Home.js和Main.js,如今文件結構變爲:
app/ .....config/ ..........routes.js .....components/ ..........Home.js ..........Main.js .....index.html .....index.js .babelrc package.json webpack.config.js
在Home.js中建立一個名稱爲Home的組件
var React = require('react'); var Home = React.createClass({ render: function(){ return ( <div>Hello From Home!</div> ) } }); module.exports = Home;
在Main.js中建立一個名稱爲Main的組件
var React = require('react'); var Main = React.createClass({ render: function(){ return ( <div> Hello From Main! {this.props.children} </div> ) } }); module.exports = Main;
配置config/routes.js中的路由,其實就是配對url和組件的映射關係。
var React = require('react'); var ReactRouter = require('react-router'); var Router = ReactRouter.Router; var Route = ReactRouter.Route; var IndexRouter = ReactRouter.IndexRoute; var Main = require('../components/Main'); var Home = require('../components/Home'); var routes = ( <Router> <Route path='/' component={Main}> <Route path='/home' component={Home} /> </Route> </Router> ); module.exports = routes;
以上,路由從本質上說也是組件。<Route path='/' component={Main}>...</Route>
依賴於<Route path='/home' component={Home} />
,當url爲/的時候,只會顯示Main組件內容,當url爲/home的時候會同時顯示Main組件和Home組件的內容。
在app/index.js中,把路由這個特殊的組件加載起來。
var React = require('react'); var ReactDOM = require('react-dom'); var routes = require('./config/routes'); ReactDOM.render( routes, document.getElementById('app') );
npm run start
在瀏覽器中輸入:localhost:8080
url變成:http://localhost:8080/#/?_k=dhjswq
內容爲:Hello From Main!
由於,當url爲/的時候,路由設置顯示Main組件,雖然在Main組件定義的時候有{this.props.children}
,但由於Main組件元素嵌套的Home組件沒有顯示,全部只顯示Main組件的內容。
在瀏覽器中輸入:http://localhost:8080/#/home?_k=dhjswq
內容:
Hello From Main!
Hello From Home!
也很好理解,由於Home組件是被嵌套在Main這個組件元素中的,當url爲Home路由的時候,不只把Home組件顯示了出來,還把Main組件顯示了出來。
若是想始終都顯示Home這個組件呢?
須要在app/config/routes.js按以下配置
var React = require('react'); var ReactRouter = require('react-router'); var Router = ReactRouter.Router; var Route = ReactRouter.Route; var hashHistory = ReactRouter.hashHistory; var IndexRouter = ReactRouter.IndexRoute; var Main = require('../components/Main'); var Home = require('../components/Home'); var routes = ( <Router history={hashHistory}> <Route path='/' component={Main}> <IndexRouter component={Home} /> </Route> </Router> ); module.exports = routes;
以上,IndexRouter表示始終都顯示的路由。
【無狀態函數式聲明組件】
如今,已經習慣了按這樣的方式聲明組件:
var HelloWorld = React.createClass({ render: function(){ return ( <div>Hello {this.props.name}</div> ) } }); ReactDOM.render(<HelloWorld name='darren' />, document.getElementById('app'));
除了上面的聲明方式,還有一種"無狀態函數式聲明方式"。當React.createClass中只有render方法的時候就能夠按以下方式來替代。
function HelloWorld(props){ return ( <div>Hello {props.name}</div> ) } ReactDOM.render(<HelloWorld name='darren' />, document.getElementById('app'));
【組件中的變量類型約定】
定義組件的時候常常用到變量,這些變量的類型能夠約定嗎?
--答案是能夠的,使用propTypes
var React = require('react'); var PropTypes = React.PropTypes; var Icon = React.createClass({ propTypes: { name: PropTypes.string.isRequired, size: PropTypes.number.isRequired, color: PropTypes.string.isRequired, style: PropTypes.object }, render: ... });
【生命週期事件】
全部的組件在生命週期內有一些共同的事件。有些事件在組件與DOM的綁定或解除綁定的時候發生,有些事件在組件接受數據的時候發生。
給組件設置一些默認屬性:
var Loading = React.createClass({ getDefaultProps: function(){ return { text: 'Loading' } }, render: function(){ ... } });
以上,經過getDefaultProps
爲組件設置了一些默認屬性和其對應的值。
設置組件的初始狀態:
var Login = React.createClass({ getInitialState: function(){ return { email: '', password:'' } }, render: function(){ ... } });
以上,經過getInitialState
設置組件的初始狀態。
組件綁定到DOM時觸發的事件
好比,當組件綁定到DOM時從遠程獲取一些數據:
var FriendsList = React.createClass({ componentDidMount: functioin(){ return Axios.get(this.props.url).then(this.props.callback); }, render: function(){ ... } });
以上,經過componentDidMount
在組件綁定到DOM上後發生事件。
好比,當組件綁定到DOM時設置監聽:
var FriendsList = React.createClass({ componenetDidMount: function(){ ref.on('value', function(snapshot){ this.setState({ friends: snapshot.val() }); }) }, render:... });
組件與DOM解除綁定時觸發的事件
var FriendList = React.createClass({ componentWillUnmount: function(){ ref.off(); }, render:... });
以上,經過componentWillUnmount
來觸發當組件與DOM解除綁定時的事件。
當組件接受到新的屬性值時觸發的事件:componentWillReceiveProps
決定組件是否須要渲染的事件:shouldComponentUpdate
全部的事件大體以下圖:
【this關鍵字】
隱式綁定
var me = { name: 'Darren', age:25, sayName: function(){ console.log(this.name); } }; me.sayName();
以上,當調用sayName
的時候,隱式用到了this,這裏的this指的是點左邊的me.
來看一個在嵌套函數中使用this關鍵字的例子。
var sayNameMixin = function(obj){ obj.sayName = function(){ console.log(this.name); }; } var me = { name: 'Darren', age: 25 }; var you = { name: 'Joey', age: 21 }; sayNameMixin(me); sayNameMixin(you); me.sayName();//Darren you.sayName();//Joey
以上,sayNameMixin接受me這個參數,而後在其內部給me定義了一個sayName方法,這裏的this也是值me。
再來看用構造函數建立對象,使用this關鍵字的例子。
var Person = function(name, age){ return { name: name, age: age, sayName: function(){ console.log(this.name); } } }; var jim = Person('Jim', 42); jim.sayName();
以上,調用sayName方法的時候隱式用到了this,這裏的this仍是指的是點左邊的jim。
因此,這裏關於隱式綁定的的總結是:當隱式調用this的時候,this一般指的是點左側的那個變量。這裏的this在定義的時候就很明確。
顯式綁定
一、使用call方法顯式指定this關鍵字。
//在定義的時候this指的誰並不明確 var sayName = function(){ console.log('My name is ' + this.name); }; var stacy = { name: 'Stacy', age: 34 }; sayName.call(stacy);
以上,在定義sayName方法的時候並無明肯定義this指的誰。使用call方法的時候把stacy傳值給了this關鍵字。
call方法不只能夠指向this,還能夠傳遞參數。
var sayName = function(lang1, lang2, lang3){ console.log('My name is ' + this.name + ' and I know ' + lang1 + ', ' + lang2 + ', and ' + lang3); }; var stacy = { name: 'Stacey', age:28 }; var languages = ['JavaScript', 'Ruby', 'Pythos']; sayName.call(stacey, languages[0], languages[1], languages[2]);
以上,經過調用call方法不只指定了this關鍵字,還傳遞了sayName方法所需的參數。
二、使用apply方法顯式綁定this關鍵字
var sayName = function(lang1, lang2, lang3){ console.log('My name is ' + this.name + ' and I know ' + lang1 + ', ' + lang2 + ', and ' + lang3); }; var stacy = { name: 'Stacey', age:28 }; var languages = ['JavaScript', 'Ruby', 'Pythos']; sayName.apply(stacey, languages);
以上,apply和call的區別可見一斑,apply接受的實參數組。
三、使用bind方法顯式綁定this關鍵字
var sayName = function(lang1, lang2, lang3){ console.log('My name is ' + this.name + ' and I know ' + lang1 + ', ' + lang2 + ', and ' + lang3); }; var stacy = { name: 'Stacey', age:28 }; var languages = ['JavaScript', 'Ruby', 'Pythos']; var newFn = sayName.bind(stacey, languages[0], languages[1], languages[2]); newFn();
以上,使用bind方法能夠產生一個新的函數,再調用該函數。
New Binding
var Animal = function(color, name, type){ //this = {} this.color = color; this.name = name; this.type = type; } var zebra = new Animal('black and white', 'Zorro', 'Zebra');
以上,當定義Animal這個函數的時候,此時的this指的是一個空對象,當實例化Animal的時候this就有值了。
Window Binding
var sayAge = function(){ console.log(this.age); } var me = { age: 25 }; sayAge(); //undefined window.age = 35; sayAge(); //35
可見,當沒有給this顯式綁定的時候,this指的是window。
接下來,會從一個例子來體會React的方方面面......