1. 幾個重要概念理解
-
模組與元件
-
模組:
- 理解: 向外提供特定(區域性)功能的js程式, 一般就是一個js檔案
- 為什麼: js程式碼更多更復雜
- 作用: 複用js, 簡化js的編寫, 提高js執行效率
-
元件:
- 理解: 用來實現特定功能效果的程式碼集合(html/css/js)
- 為什麼: 一個介面的功能更復雜
- 作用: 複用編碼, 簡化專案編碼, 提高執行效率
-
-
模組化與元件化
-
模組化:
- 當應用的js都以模組來編寫的, 這個應用就是一個模組化的應用
-
元件化:
- 當應用是以多元件的方式實現功能, 這上應用就是一個元件化的應用
-
2. React的基本認識
- Facebook開源的一個js庫
- 一個用來動態構建使用者介面的js庫
-
React的特點
- Declarative(宣告式編碼)
- Component-Based(元件化編碼)
- Learn Once, Write Anywhere(支援客戶端與伺服器渲染)
- 高效
- 單向資料流
-
React高效的原因
- 虛擬(virtual)DOM, 不總是直接操作DOM(批量更新, 減少更新的次數)
- 高效的DOM Diff演算法, 最小化頁面重繪(減小頁面更新的區域)
3. 使用React
* 匯入相關js庫檔案(react.js, react-dom.js, babel.min.js)
* 編碼:
```
<div id="container"></div>
<script type="text/babel">
var aa = 123
ReactDOM.render(<h1>{aa}</h1>, containerDOM);
</script>
```
4. JSX
- 全稱: JavaScript XML
-
react定義的一種類似於XML的JS擴充套件語法: XML+JS
-
作用: 用來建立react虛擬DOM(元素)物件
- js中直接可以套標籤, 但標籤要套js需要放在{}中
- 在解析顯示js陣列時, 會自動遍歷顯示
-
把資料的陣列轉換為標籤的陣列:
var liArr = dataArr.map(function(item, index){ return <li key={index}>{item}</li> })
-
-
注意:
- 標籤必須有結束
- 標籤的class屬性必須改為className屬性
- 標籤的style屬性值必須為: {{color:`red`, width:12}}
5. Component : React是面向元件程式設計的(元件化編碼開發)
* 基本理解和使用
* 自定義的標籤: 元件類(函式)/標籤
* 建立元件類
```
//方式1: 無狀態函式(最簡潔, 推薦使用)
function MyComponent1() {
return <h1>自定義元件標題11111</h1>;
}
//方式2: ES6類語法(複雜元件, 推薦使用)
class MyComponent3 extends React.Component {
render () {
return <h1>自定義元件標題33333</h1>;
}
}
//方式3: ES5老語法(不推薦使用了)
var MyComponent2 = React.createClass({
render () {
return <h1>自定義元件標題22222</h1>;
}
});
```
* 渲染元件標籤
```
ReactDOM.render(<MyComp />, cotainerEle);
```
* ReactDOM.render()渲染元件標籤的基本流程
* React內部會建立元件例項物件/呼叫元件函式, 得到虛擬DOM物件
* 將虛擬DOM並解析為真實DOM
* 插入到指定的頁面元素內部
* props
* 所有元件標籤的屬性的集合物件
* 給標籤指定屬性, 儲存外部資料(可能是一個function)
* 在元件內部讀取屬性: this.props.propertyName
* 作用: 從目標元件外部向元件內部傳遞資料
* 對props中的屬性值進行型別限制和必要性限制
```
Person.propTypes = {
name: React.PropTypes.string.isRequired,
age: React.PropTypes.number.isRequired
}
```
* 擴充套件屬性: 將物件的所有屬性通過props傳遞
```
<Person {...person}/>
```
* 元件的組合
* 元件標籤中包含子元件標籤
* 拆分元件: 拆分介面, 抽取元件
* 通過props傳遞資料
* refs
* 元件內包含ref屬性的標籤元素的集合物件
* 給操作目標標籤指定ref屬性, 打一個標識
* 在元件內部獲得標籤物件: this.refs.refName(只是得到了標籤元素物件)
* 作用: 操作元件內部的真實標籤dom元素物件
* 事件處理
* 給標籤新增屬性: onXxx={this.eventHandler}
* 在元件中新增事件處理方法
```
eventHandler(event) {
}
```
* 使自定義方法中的this為元件物件
* 在constructor()中bind(this)
* 使用箭頭函式定義方法(ES6模組化編碼時才能使用)
* state
* 元件被稱為"狀態機", 頁面的顯示是根據元件的state屬性的資料來顯示
* 初始化指定:
```
constructor() {
super();
this.state = {
stateName1 : stateValue1,
stateName2 : stateValue2
};
}
```
* 讀取顯示:
this.state.stateName1
* 更新狀態-->更新介面 :
this.setState({stateName1 : newValue})
* 實現一個雙向繫結的元件
* React是單向資料流
* 需要通過onChange監聽手動實現
* 元件生命週期
* 元件的三個生命週期狀態:
* Mount:插入真實 DOM
* Update:被重新渲染
* Unmount:被移出真實 DOM
* 生命週期流程:
* 第一次初始化顯示
```
constructor()
componentWillMount() : 將要插入回撥
render() : 用於插入虛擬DOM回撥
componentDidMount() : 已經插入回撥
```
* 每次更新state
```
componentWillReceiveProps(): 接收父元件新的屬性
componentWillUpdate() : 將要更新回撥
render() : 更新(重新渲染)
componentDidUpdate() : 已經更新回撥
```
* 刪除元件
```
ReactDOM.unmountComponentAtNode(document.getElementById(`example`)) : 移除元件
componentWillUnmount() : 元件將要被移除回撥
```
* 常用的方法
```
render(): 必須重寫, 返回一個自定義的虛擬DOM
constructor(): 初始化狀態, 繫結this(可以箭頭函式代替)
componentDidMount() : 只執行一次, 已經在dom樹中, 適合啟動/設定一些監聽
```
6. ajax
* React沒有ajax模組
* 整合其它的js庫(如axios/fetch/jQuery/), 傳送ajax請求
* axios
* 封裝XmlHttpRequest物件的ajax
* promise
* 可以用在瀏覽器端和伺服器
* fetch
* 不再使用XmlHttpRequest物件提交ajax請求
* fetch就是用來提交ajax請求的函式, 只是新的瀏覽才內建了fetch
* 為了相容低版本的瀏覽器, 可以引入fetch.js
* 在哪個方法去傳送ajax請求
* 只顯示一次(請求一次): componentDidMount()
* 顯示多次(請求多次): componentWillReceiveProps()
7. 虛擬DOM
-
虛擬DOM是什麼?
- 一個虛擬DOM(元素)是一個一般的js物件, 準確的說是一個物件樹(倒立的)
- 虛擬DOM儲存了真實DOM的層次關係和一些基本屬性,與真實DOM一一對應
- 如果只是更新虛擬DOM, 頁面是不會重繪的
-
Virtual DOM 演算法的基本步驟
- 用 JavaScript 物件結構表示 DOM 樹的結構;然後用這個樹構建一個真正的 DOM 樹,插到文件當中
- 當狀態變更的時候,重新構造一棵新的物件樹。然後用新的樹和舊的樹進行比較,記錄兩棵樹差異
- 把2所記錄的差異應用到步驟1所構建的真正的DOM樹上,檢視就更新了
-
進一步理解
- Virtual DOM 本質上就是在 JS 和 DOM 之間做了一個快取。
- 可以類比 CPU 和硬碟,既然硬碟這麼慢,我們就在它們之間加個快取:既然 DOM 這麼慢,我們就在它們 JS 和 DOM 之間加個快取。CPU(JS)只操作記憶體(Virtual DOM),最後的時候再把變更寫入硬碟(DOM)。
8. 使用React腳手架建立一個React應用
-
react腳手架
-
xxx腳手架: 用來幫助程式設計師快速建立一個基於xxx庫的空專案的庫
- 包含了所有需要的配置
- 指定好了所有的依賴
- 可以直接安裝/編譯/執行一個簡單效果
- react提供了一個專門用於建立react專案的腳手架庫: create-react-app
- 專案的整體技術架構為: react + webpack + es6 + eslint
-
-
建立專案並啟動
- npm install -g create-react-app
- create-react-app hello-react
- cd hello-react
- npm start
9. app1: 實現一個評論管理功能
-
拆分元件:
- 應用元件: App
- 新增評論元件: CommentAdd
- 評論項元件: CommentItem
- 評論列表元件: CommentList
-
確定元件的state和props:
-
App:
- state: comments/array
-
CommentAdd
- state: username/string, content/string
- props: add/func
-
commentList
- props: comments/array, delete/func
-
CommentItem
- props: comment/object, delete/func, index/number
-
-
編寫靜態元件
- 拆分頁面
- 拆分css
-
實現動態元件
-
動態展示初始化資料
- 初始化狀態資料
- 傳遞屬性資料
-
響應使用者操作, 更新元件介面
- 繫結事件監聽, 並處理
- 更新state
-
10. app2: 實現github使用者搜尋功能
-
拆分元件
- App
- Search
- List
-
確定元件的state和props
-
App
- state: searchName/string
-
Search
- props: setSearchName/func
-
List
- props: searchName/string
- state: firstView/bool, loading/bool, users/array, errMsg/string
-
- 編寫靜態元件
-
編寫動態元件
- componentWillReceiveProps(nextProps): 監視接收到新的props, 傳送ajax
- 使用axios庫傳送ajax請求
11. 元件間通訊總結
-
方式一: 通過props傳遞
- 共同的資料放在父元件上, 特有的資料放在自己元件內部(state)
- 通過props可以傳遞一般資料和函式資料, 只能一層一層傳遞
- 一般資料–>父元件傳遞資料給子元件–>子元件讀取資料
- 函式資料–>子元件傳遞資料給父元件–>子元件呼叫函式
-
方式二: 使用訊息訂閱(subscribe)-釋出(publish)機制: 自定義事件機制
- 工具庫: PubSubJS
- 下載: npm install pubsub-js –save
-
使用:
import PubSub from `pubsub-js` //引入 PubSub.subscribe(`delete`, function(data){ }); //訂閱 PubSub.publish(`delete`, data) //釋出訊息
12. ES6新語法
- const/let
- 解構賦值: let {a, b} = this.props
- 物件的簡潔表達
-
箭頭函式:
- 元件的自定義方法: xxx = () => {}
- map/filter的回撥方法: (item, index) => {}
-
優點:
- 簡潔
- 沒有自己的this,使用引用this查詢的是外部this
-
擴充套件運算子(…)
- 解構物件: const MyProps = {}, <Xxx {…MyProps}>
- class/extends/constructor/super
- ES6模組化(Babel)
13. 專案打包執行
-
專案編譯打包並執行
- npm build
- npm install -g pushstate-server
- pushstate-server build
- 訪問: http://localhost:9000
14. 下載相關模組包
- 建立package.json
-
react相關庫
npm install react react-dom --save
-
babel相關庫
npm install babel-core babel-preset-es2015 babel-preset-react --save-dev
-
webpack相關庫
npm install webpack babel-loader --save-dev npm install webpack-dev-server
15. webpack配置檔案: webpack.config.js
const path = require(`path`); //path內建的模組,用來設定路徑。
module.exports = {
entry: `./src/main.js`, // 入口檔案
output: { // 輸出配置
filename: `bundle.js`, // 輸出檔名
path: path.resolve(__dirname, `dist`) //輸出檔案路徑配置
},
module: {
rules: [
{
test: /.css$/,
use: [
`style-loader`,
`css-loader`
]
},
//babel處理js
{
test: /.js$/,
exclude: /node_modules/, //排除此資料夾
use: [
`babel-loader`
]
}
]
}
};
16. babel配置檔案: .babelrc
{
"presets": ["es2015", "react"]
}
17. 編碼
-
src/js/App.js: 應用元件
import React from `react` export default function App() { //暴露元件都得使用預設暴露 return <h1>Hello React Client Component</h1> }
-
src/js/main.js: 入口js
import React from `react` import ReactDOM from `react-dom` import App from `./App` //渲染元件標籤到頁面元素 ReactDOM.render(<App />, document.getElementById(`demo`))
18. 下載css載入器
npm install style-loader css-loader --save-dev
建立css檔案 src/css/test.css
body{
background : red
}
19. 配置webpack-dev-server
devServer:{
contentBase: `./`,//內建伺服器動態載入頁面所在的目錄
}
``
##20. 執行命令
構建任務:webpack
熱載入任務: webpack-dev-server
```
21 package.json: 新增編譯/執行指令碼
"scripts": {
"start": "webpack-dev-server",
"build": "webpack"
}
react-router使用教程
0. 關於url中#的作用:
- 學習: http://www.ruanyifeng.com/blo…
- `#`代表網頁中的一個位置。其右面的字元,就是該位置的識別符號
- 改變#不觸發網頁過載
- 改變#會改變瀏覽器的訪問歷史
- window.location.hash讀取#值
- window.onhashchange = func 監聽hash改變
1. reat-router
- github主頁: https://github.com/ReactTrain…
- 官網教程: https://github.com/reactjs/re…
- 一峰教程: http://www.ruanyifeng.com/blo…
2. react-router庫中的相關元件
-
包含的相關元件:
- Router: 路由器元件, 用來包含各個路由元件
- Route: 路由元件, 註冊路由
- IndexRoute: 預設路由元件
- hashHistory: 路由的切換由URL的hash變化決定,即URL的#部分發生變化
- Link: 路由連結元件
-
Router: 路由器元件
- 屬性: history={hashHistory} 用來監聽瀏覽器位址列的變化, 並將URL解析成一個地址物件,供React Router匹配
- 子元件: Route
-
Route: 路由元件
- 屬性1: path=”/xxx”
- 屬性2: component={Xxx}
- 根路由元件: path=”/”的元件, 一般為App
- 子路由元件: 子<Route>配置的元件
-
IndexRoute: 預設路由
- 當父路由被請求時, 預設就會請求此路由元件
-
hashHistory
- 用於Router元件的history屬性
- 作用: 為地址url生成?_k=hash, 用於內部儲存對應的state
-
Link: 路由連結
- 屬性1: to=”/xxx”
- 屬性2: activeClassName=”active”
3. 配置(從官方教程樣例中拷貝)
* webpack配置: webpack.config.js
```
module.exports = {
//入口js
entry: `./index.js`,
//編譯打包輸出
output: {
filename: `bundle.js`,
publicPath: ``
},
module: {
//使用的loaders
loaders: [
{test: /.js$/, exclude: /node_modules/, loader: `babel-loader?presets[]=es2015&presets[]=react`}
]
}
}
```
* 包配置: package.json
```
{
"name": "tutorial",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"start": "webpack-dev-server --inline --content-base ."
},
"author": "",
"license": "ISC",
"dependencies": {
"react": "^0.14.7",
"react-dom": "^0.14.7",
"react-router": "^2.0.0"
},
"devDependencies": {
"babel-core": "^6.5.1",
"babel-loader": "^6.2.2",
"babel-preset-es2015": "^6.5.0",
"babel-preset-react": "^6.5.0",
"http-server": "^0.8.5",
"webpack": "^1.12.13",
"webpack-dev-server": "^1.14.1"
}
}
```
4. 編碼
* 定義各個路由元件
* About.js
```
import React from `react`
function About() {
return <div>About元件內容</div>
}
export default About
```
* Home.js
```
import React from `react`
function Home() {
return <div>Home元件內容2</div>
}
export default Home
```
* Repos.js
```
import React, {Component} from `react`
export default class Repos extends Component {
render() {
return (
<div>Repos元件</div>
)
}
}
```
* 定義應用元件: App.js
```
import React, {Component} from `react`
import {Link} from `react-router`
export default class App extends Component {
render() {
return (
<div>
<h2>Hello, React Router!</h2>
<ul>
<li><Link to="/about" activeClassName="active">About2</Link></li>
<li><Link to="/repos" activeClassName="active">Repos2</Link></li>
</ul>
{this.props.children}
</div>
)
}
}
```
* 定義入口JS: index.js-->渲染元件
```
import React from `react`
import {render} from `react-dom`
import {Router, Route, IndexRoute, hashHistory} from `react-router`
import App from `./modules/App`
import About from `./modules/About`
import Repos from `./modules/Repos`
import Home from `./modules/Home`
render((
<Router history={hashHistory}>
<Route path="/" component={App}>
<IndexRoute component={Home}/>
<Route path="/about" component={About}></Route>
<Route path="/repos" component={Repos}></Route>
</Route>
</Router>
), document.getElementById(`app`))
```
* 主頁面: index.html
```
<style>
.active {
color: red;
}
</style>
<div id=app></div>
<script src="bundle.js"></script>
```
5. 傳遞請求引數
* repo.js: repos元件下的分路由元件
```
import React from `react`
export default function ({params}) {
let {username, repoName} = params
return (
<div>使用者名稱:{username}, 倉庫名:{repoName}</div>
)
}
```
* repos.js
```
import React from `react`
import NavLink from `./NavLink`
export default class Repos extends React.Component {
constructor(props) {
super(props);
this.state = {
repos: [
{username: `faceback`, repoName: `react`},
{username: `faceback`, repoName: `react-router`},
{username: `Angular`, repoName: `angular`},
{username: `Angular`, repoName: `angular-cli`}
]
};
this.handleSubmit = this.handleSubmit.bind(this)
}
handleSubmit () {
const repos = this.state.repos
repos.push({
username: this.refs.username.value,
repoName: this.refs.repoName.value
})
this.setState({repos})
this.refs.username.value = ``
this.refs.repoName.value = ``
}
render() {
return (
<div>
<h2>Repos</h2>
<ul>
{
this.state.repos.map((repo, index) => {
const to = `/repos/${repo.username}/${repo.repoName}`
return (
<li key={index}>
<Link to={to} activeClassName=`active`>{repo.repoName}</Link>
</li>
)
})
}
<li>
<form onSubmit={this.handleSubmit}>
<input type="text" placeholder="使用者名稱" ref=`username`/> / {` `}
<input type="text" placeholder="倉庫名" ref=`repoName`/>{` `}
<button type="submit">新增</button>
</form>
</li>
</ul>
{this.props.children}
</div>
);
}
}
```
* index.js: 配置路由
```
<Route path="/repos" component={Repos}>
<Route path="/repos/:username/:repoName" component={Repo}/>
</Route>
```
6. 優化Link元件
* NavLink.js
```
import React from `react`
import {Link} from `react-router`
export default function NavLink(props) {
return <Link {...props} activeClassName="active"/>
}
```
* Repos.js
```
<NavLink to={to}>{repo.repoName}</NavLink>
```
使用開源的ant-design庫開發專案指南
1. 最流行的開源React UI元件庫
-
material-ui(國外)
-
ant-design(國內螞蟻金服)
- 官網: https://ant.design/
- github: https://github.com/ant-design…
2. ant-design使用入門
使用create-react-app搭建react開發環境
npm install create-react-app -g
create-react-app antd-demo
cd antd-demo
npm start
搭建antd的基本開發環境
-
下載
npm install antd --save
-
src/App.js
import React, { Component } from `react`; import { Button } from `antd`; import `./App.css`; class App extends Component { render() { return ( <div className="App"> <Button type="primary">Button</Button> </div> ); } } export default App;
-
src/App.css
@import `~antd/dist/antd.css`; .App { text-align: center; }
實現按需載入(css/js)
-
使用 eject 命令將所有內建的配置暴露出來
npm run eject
-
下載babel-plugin-import(用於按需載入元件程式碼和樣式的 babel 外掛)
npm install babel-plugin-import --save-dev
-
修改配置: config/webpack.config.dev.js
// Process JS with Babel. { test: /.(js|jsx)$/, include: paths.appSrc, loader: `babel`, query: { + plugins: [ + [`import`, [{ libraryName: "antd", style: `css` }]], + ], // This is a feature of `babel-loader` for webpack (not Babel itself). // It enables caching results in ./node_modules/.cache/babel-loader/ // directory for faster rebuilds. cacheDirectory: true } },
-
去除引入全量樣式的語句: src/App.css
@import `~antd/dist/antd.css`
願你成為終身學習者