react開發:從零開始搭建一個react專案
從頭開始建立一個React App - 專案基本配置
npm init
生成package.json
檔案.- 安裝各種需要的依賴:
npm install --save react
- 安裝React.npm install --save react-dom
安裝React Dom,這個包是用來處理virtual DOM。這裡提一下用React Native的話,這裡就是安裝react-native。npm install --save-dev webpack
- 安裝Webpack, 現在最流行的模組打包工具.npm install --save-dev webpack-dev-server
npm install --save-dev babel-core
- 安裝Babel, 可以把ES6轉換為ES5,注意Babel最新的V6版本分為babel-cli和babel-core兩個模組,這裡只需要用babel-cor即可。- 安裝其他的babel依賴(babel真心是一個全家桶,具體的介紹去官網看吧..我後面再總結,這裡反正全裝上就是了):
npm install --save babel-polyfill
- Babel includes a polyfill that includes a custom regenerator runtime and core.js. This will emulate a full ES6 environmentnpm install --save-dev babel-loader
- webpack中需要用到的loader.npm install --save babel-runtime
- Babel transform runtime 外掛的依賴.npm install --save-dev babel-plugin-transform-runtime
- Externalise references to helpers and builtins, automatically polyfilling your code without polluting globals.npm install --save-dev babel-preset-es2015
npm install --save-dev babel-preset-react
- Strip flow types and transform JSX into createElement calls.npm install --save-dev babel-preset-stage-2
- All you need to use stage 2 (and greater) plugins (experimental javascript).
-
開啟
package.json
然後新增下面的scripts:"scripts": { "start": "webpack-dev-server --hot --inline --colors --content-base ./build", "build": "webpack --progress --colors" }
命令列輸入
npm start
將要啟動webpack dev server.命令列輸入
npm build
將會進行生產環境打包. -
啟動webpack
Webpack是我們的打包工具,在我們的開發環境中具體很重要的作用,具有很多非常便捷的特性,尤其是熱載入hot reloading.
webpack.config.js
是如下所示的webpack的配置檔案. 隨著app的不斷變化,配置檔案也會不斷的更新,這裡我們就用預設的webpack.config.js
來命名這個配置檔案,假如你用別的名字比如webpack.config.prod.js
那麼上面的指令碼build就需要相應的改變指定相應的配置檔名字:"build": "webpack webpack.config.prod.js --progress --colors"
var webpack = require('webpack'); module.exports = { entry: './src/app.js', output: { path: __dirname + '/build', filename: "bundle.js" }, module: { rules: [{ test: /\.js$/, exclude: /node_modules/, loader: 'babel-loader', query: { plugins: ['transform-runtime'], presets: ['es2015', 'react', 'stage-2'] } }, { test: /\.css$/, loader: "style-loader!css-loader" }] } };
- OK,我們專案的基本配置終於完成了,是時候開始寫Reac程式碼了.
React 基礎 - 建立你的第一個Component
在上面的專案的基本配置基礎上,我們開始書寫React的第一個元件來熟悉React的寫法與元件思想。
-
首先我們在專案根目錄中新建一個
index.html
檔案。 在這個基礎工程中, 我們使用bootstrap的樣式,直接引入一個cdn即可. 然後新增一個html標籤<div id="app"></div>
,我們的app就會注入到這個div中。 最後再引入<script src="bundle.js"></script>
,這是最後打包生成的js程式碼。以下是完整的程式碼:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Document</title> <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css"> </head> <body> <div id="app"></div> <script src="bundle.js"></script> </body> </html>
- 建立一個新的資料夾
src
. 我們app的大部分程式碼都將放在這個資料夾裡面。在src
中建立app.js
,作為React App的根元件, 其他所有的元件都會注入到這個跟元件中。 -
首先我們需要匯入react,現在都已經用ES6的語法,
import React from 'react';
, 然後我們要引入react-dom. 這裡面有react中最重要的一個虛擬dom的概念.引入程式碼:import ReactDOM from 'react-dom';
-
現在需要引入的依賴都已經完畢我們可以寫第一個元件了:
class App extends React.Component { render(){ // Every react component has a render method. return( // Every render method returns jsx. Jsx looks like HTML, but it's actually javascript and functions a lot like xml, with self closing tags requiring the `/` within the tag in order to work propperly <div> Hello World </div> ); } }
注意這裡"Hello World"寫在
div
中. 所有的jsx程式碼都需要寫在一個父div中. -
最後我們需要把我們寫好的元件render給Dom,這裡就需要用到
ReactDOM.render
方法.在
App.js
的下面新增:ReactDOM.render(<App />, document.getElementById('app'));
第一個引數就是我們App的根元件, 寫作
<App />
的形式. 第二個引數就是我們的APP將要主要的DOM元素. 在這個專案中,就是我們在index中寫的id為app
的div
標籤。
Ok,我們的APP結構已經出來了,經典的hello world已經實現。馬上我們就在這個基礎上再實現經典的todo app。大致的原型就有一個輸入框用來輸入代辦事項然後新增到事件列表中。事件列表中每一個代辦事項被點選就會標註一條刪除線表示完成,點選後面的刪除按鈕則會將其從列表中刪除。通過完成這個APP的過程你將學會一個完整的react app的所有的基本構建塊。
生命週期方法和兩種形式的元件構建
我們從一些小的模組開始起步.一個元件component就是一個react app的構件塊. 有兩種形式的元件: 類元件(Class)和函式型元件(Functional). 在這個專案中,這兩種形式的元件我們都會使用, 並且使用生命週期的鉤子,同時也會使用state和props兩個react中重要的屬性。
-
首先在
src
資料夾中新建components
資料夾,你的檔案結構就是這樣~/src/components
。 -
然後在
components
中新建檔案ToDoApp.js
。 對於所有的react元件我們都需要在頭部引入reactimport React from 'react';
。 -
下面我們寫一個類元件. 所有的class 元件有一個render方法用來返回jsx。
ToDoApp的class就如下所示:
class ToDoApp extends React.Component { render() { return ( <div>To Do App</div> ); } }
-
為了將這個元件注入到我們的APP中, 首先我們需要輸出它。 在這個元件程式碼底部新增
export default ToDoApp;
。 -
然後在
app.js
頂部我們新增import ToDoApp from '.components/ToDoApp';
匯入元件用來代替Hello World
。 render中替換為新的jsx程式碼<ToDoApp />
半閉合型別的標籤即可。 -
然後在瀏覽器中你就可以看到"To Do App" 代替了原來的 "Hello World"!這樣我們就完成了將第一個子元件嵌入到根元件之中了,這就是構建react app的常規模式。下面繼續完善我們的元件。
-
返回到
ToDoApp
中來構建我們的第一個代辦事項列表。首先我們使用bootstrap來構建比較方便且美觀。 用下面的jsx替換當前render
方法中return
中的jsx:<div className="row"> <div className="col-md-10 col-md-offset-1"> <div className="panel panel-default"> <div className="panel-body"> <h1>My To Do App</h1> <hr/> List goes here. </div> </div> </div> </div>
-
現在開啟瀏覽器, 你將會看到一個標題 "My To Do App" 下面跟隨一個bootstrap的panel元件裡面寫有 "List Goes Here",我們將在這個地方構建列表。 那麼我們如何將資料儲存在我們的列表中呢? 答案就是使用
state
. 每一個類元件都有state
屬性,可以通過this.state
在元件任何位置獲取並且用this.setState({ key: "value" })
這種方法來更新狀態。但是除非必要我們比較少使用state
,這裡暫時先使用作為了解,後期會使用redux來管理狀態。在
ToDoApp
中我們可以使用許多生命週期方法的鉤子, 其中一個就是componentWillMount
。 這個方法的執行是在頁面載入並且render方法之前。可以在其中獲取列表資料,在我們的APP中直接用一個虛擬的陣列提供。(值得注意的是componentWillMount會引起很多小問題,因此真實專案中儘量不要使用,而是應該用componentDidMount)。
在ToDoApp
中render
方法之前新增:componentWillMount(){ // run before the render method this.setState({ // add an array of strings to state. list: ['thing1', 'thing2', 'thing3'] }) };
現在我們獲取了一個虛擬列表,需要重點注意的就是react依賴於state和props,只有當state和props改變的時候react元件才會重新整理。
-
現在我們新增列表到這個view裡,這裡不是直接簡單的在裡面修改jsx,而是再建立一個新的元件來構建列表,這次我們學習使用函式型元件,需要注意的是函式型元件沒有生命週期方法和state屬性,它僅僅是一個返回jsx的函式,並且引數是props。
那麼props到底是什麼呢?props是從父元件傳遞進子元件的資料的名字,這是一個很重要的概念,也是react app資料傳遞的最典型與最推薦的方法。通常我們將資料保持在app的頂端元件,通過元件讓資料流下來保證APP的精確執行。這些資料和props的一些處理可能會影響APP的執行,但是假如你按照這個課程的實踐流程來做,這些影響都會很小。
再新建一個
components
資料夾並在其中新建一個List.js
作為我們要建立的函式型元件。用const來新建一個函式,引數名字寫作props。
函式形式如下所示:const List = (props) => { // we're using an arrow function and const variable type, a ES6 features return ( <div> I'm a list!!! </div> ) }; export default List;
-
在
ToDoApp.js
引入 List用List
元件替換List goes here.
,寫法為<List />
.現在在瀏覽器中就可以看到"I'm a list!!!"現在我們來把這個變成真實的列表,首先就需要通過props傳遞資料,我們把這個從state中獲取的資料list通過命名為listItems的props傳遞,寫作:
<List listItems={this.state.list} />
,現在List
已經通過props獲取了ToDoApp
中的資料。然後在
List
元件中我們需要render一個列表,先用下面的jsx程式碼代替:<div> <ul> { list // this is a variable we'll define next } </ul> </div>
注意這個大括號,js可以在這裡面執行並將返回新增到view裡。首先我們定義一個列表變數:
const list = props.listItems.map((el, i)=>( // All where doing here is getting the items listItems prop // (which is stored in the state of the parent component) // which is an array, and we're running the .map method // which returns a new array of list items. The key attribute is // required, and must be unique. <li key={i}><h2>el</h2></li> ));
完整的元件如下:
import React from 'react'; const List = (props) => { const list = props.listItems.map((el, i)=>( <li key={i}><h2>el</h2></li> )); return ( <div> <ul> { list } </ul> </div> ) }; export default List;
-
現在開啟瀏覽器就可以看到一列列表了。接下來就是給我們的專案加入功能了,包括新增新的事項,標註事項完成和刪除列表中事項。
給APP新增功能
1.函式型元件
首先我們需要新增一個input元素以便可以輸入代辦事項。因此我們在components
資料夾中新建一個Input.js
,然後在其中建立並輸出一個名叫Input的函式型元件。
把下面的jsx程式碼貼上到你的函式型元件return之中:
<form>
<div
className="form-group">
<label
htmlFor="listInput">
Email address
</label>
<input
type="text"
className="form-control"
id="listItemInput"
placeholder="Add new todo"
/>
<button
className="btn btn-primary">
Add Item
</button>
</div>
</form>
2. Input
現在我們的jsx沒有做任何特殊的事情,僅僅是一個基本的html檢視,不過我們先測試一下把其匯入到ToDoApp.js
,形式就是<Input/>
。
這時候會發現一個輸入框和按鈕的檢視,這個元件的靜態檢視已經寫好了,下面就需要新增功能了。
3. Props
首先我們需要做的是如何獲取輸入框的值,因為這個輸入框的值需要在其他元件中獲取,所以我們並不想要在Input
元件中來處理這個資料儲存。事實上,在子元件中儲存資料在任何時候都是不推薦的,我們應該將資料儲存在app的頂端元件並且通過props傳遞下來。
另一個需要記住的是即使我們目前把資料儲存在了上層的 ToDoApp
元件,後期還是會用redux來代替來處理整個app的資料。這裡先僅僅使用react的state來實現。
ok,我們在ToDoApp
的 componentWillMount
的setState
中新增一個newToDo
屬性用來儲存輸入框的值。
componentWillMount(){
this.setState({
list: ['thing1', 'thing2', 'thing3'],
newToDo: 'test'
})
};
同樣的就可以通過在<Input />
上通過props傳遞下去。
4. 解構(Destructuring)
在Input.js
中我們通過引數props可以獲得上級元件傳遞下來的值, 但是還可以用ES6的新特性解構來作為引數,這樣看起來更加酷!
把Input
元件的props
引數修改為({
value })
這樣的引數形式,這樣可以把props
這個物件引數解構為一個個鍵值對。直接看個小例子來就很明白了:
var props = {
name: 'hector',
age: 21
}
function log(props){
console.log(props.name);
console.log(props.age);
}
log(props);
is the same as this:
let props = {
name: 'hector',
age: 21
}
log = ({name, age}) => {
console.log(name);
console.log(age);
}
log(props);
5. setState
上面的newToDo
僅僅是添加了一個state用來儲存輸入框的值,給定一個值,輸入框就會顯示,明顯還不是我們要的效果,我們需要做的是基於輸入框的值的改變來動態改變這個state。
為了實現這個功能,我們需要再新增一個onChange
方法同樣利用props傳進Input
元件: onChange={onChange}
,
然後解構引數就是({ onChange, value })
。
然後在 ToDoApp.js
的componentWillMount
新增一個新的方法 onInputChange
。這個方法有一個引數event
,
它將要捕獲使用者在輸入框輸入的值。
onInputChange = (event) => {
this.setState({ newToDo: event.target.value}); // updates state to new value when user changes the input value
};
6. 新增新列表事項
現在需要向列表中新增新的事項,也就是在提交後能把輸入框的值儲存並顯示到列表中。我們需要再新建一個onInputSubmit
的方法,引數同樣是event
,函式體內首先需要寫 event.preventDefault()
,然後用 setState
方法把新事項新增到列表陣列中,但是,一定要注意我們的state應該是immutable的,這是react中必須遵循的一個準則,這樣才能保證對比性與可靠性。
為了實現這個功能, 需要用到