歡迎關注公眾號:n平方 如有問題或建議,請後臺留言,我會盡力解決你的問題。
背景
當你覺得原生js程式碼亂七八糟的時候,那就是要體驗一下React。(祕籍在最後)
目標
- 踢開React的大門。
簡介
React 的核心思想是:封裝元件。 各個元件維護自己的狀態和 UI,當狀態變更,自動重新渲染整個元件。 React 大體包含下面這些概念:
- 元件:
- JSX
- Virtual DOM
- Data Flow
經驗: 前端框架的基本組成: 元件及其生命週期、樣式、路由、網路請求、事件處理、資料儲存和傳遞。
HelloWorld
import React, { Component } from 'react';
import { render } from 'react-dom';
class HelloMessage extends Component {
render() {
return <div>Hello {this.props.name}</div>;
}
}
// 載入元件到 DOM 元素 mountNode
render(<HelloMessage name="John" />, mountNode);
複製程式碼
解析:
-
元件:HelloMessage 就是一個 React 構建的元件,最後一句 render 會把這個元件顯示到頁面上的某個元素 mountNode 裡面,顯示的內容就是
Hello John。 -
JSX: 將 HTML 直接嵌入了 JS 程式碼裡面(上面的js裡就寫了個div),這個就是 React 提出的一種叫 JSX 的語法.
-
Virtual DOM:
當元件狀態 state 有更改的時候,React 會自動呼叫元件的 render 方法重新渲染整個元件的 UI。 當然如果真的這樣大面積的操作 DOM,效能會是一個很大的問題,所以 React 實現了一個Virtual DOM,元件 DOM 結構就是對映到這個 Virtual DOM 上,React 在這個 Virtual DOM 上實現了一個 diff 演算法,當要重新渲染元件的時候,會通過 diff 尋找到要變更的 DOM 節點,再把這個修改更新到瀏覽器實際的 DOM 節點上,所以實際上不是真的渲染整個 DOM 樹。這個 Virtual DOM 是一個純粹的 JS 資料結構,所以效能會比原生 DOM 快很多。
- Data Flow: “單向資料繫結”是 React 推崇的一種應用架構的方式。
與webpack結合
package.json看依賴
{
"name": "learning-01",
"version": "1.0.0",
"description": "",
"main": "webpack.config.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "webpack-dev-server --config ./webpack.config.js --mode development --open",
"build": "webpack"
},
"author": "",
"license": "ISC",
"devDependencies": {
"@babel/core": "^7.3.3",
"@babel/preset-env": "^7.3.1",
"@babel/preset-react": "^7.0.0",
"babel-loader": "^8.0.5",
"clean-webpack-plugin": "^1.0.1",
"html-webpack-plugin": "^3.2.0",
"react-hot-loader": "^4.7.1",
"webpack": "^4.29.5",
"webpack-cli": "^3.2.3",
"webpack-dev-server": "^3.2.0"
},
"dependencies": {
"react": "^16.8.3",
"react-dom": "^16.8.3"
}
}
複製程式碼
webpack.config.js看配置
const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');
module.exports = {
entry: './src/index.js',
output: {
path: __dirname + '/dist',
publicPath: '/',
filename: 'bundle.js'
}, module: {
rules: [
{
test: /\.(js|jsx)$/,
exclude: /node_modules/,
use: ['babel-loader']
}
]
},
resolve: {
extensions: ['*', '.js', '.jsx']
},
plugins: [
new CleanWebpackPlugin(['dist']),
new webpack.HotModuleReplacementPlugin(),
new HtmlWebpackPlugin({template:'index.html'})
],
devServer: {
contentBase: './dist',
hot: true
}
};
複製程式碼
基本搭建環境參考(可直接clone)
JSX
-
- HTML 裡的 class 在 JSX 裡要寫成 className,因為 class 在 JS 裡是保留關鍵字。
- 2.同理某些屬性比如 for 要寫成 htmlFor。
- 3.屬性值使用表示式,只要用 {} 替換 ""
// Input (JSX):
var person = <Person name={window.isLoggedIn ? window.name : ''} />;
// Output (JS):
var person = React.createElement(
Person,
{name: window.isLoggedIn ? window.name : ''}
);
複製程式碼
-
- 使用註釋要用 {} 包起來。
{/* child comment, put {} around */}
複製程式碼
- 5.React 會將所有要顯示到 DOM 的字串轉義,防止 XSS。
<div dangerouslySetInnerHTML={{__html: 'cc © 2015'}} />
複製程式碼
- 6.屬性擴散
var props = {};
props.foo = x;
props.bar = y;
var component = <Component {...props} />;
複製程式碼
元件
生命週期(主要兩個)
componentWillMount 只會在裝載之前呼叫一次,在 render 之前呼叫,你可以在這個方法裡面呼叫 setState 改變狀態,並且不會導致額外呼叫一次 render
componentDidMount 只會在裝載完成之後呼叫一次,在 render 之後呼叫,從這裡開始可以通過 ReactDOM.findDOMNode(this) 獲取到元件的 DOM 節點。
var React = require('react');
var ReactDOM = require('react-dom');
import ComponentHeader from './components/ComponentHeader';
import ComponentFooter from './components/ComponentFooter';
import BodyIndex from './components/BodyIndex';
import BasicExample from './root'
export default class Index extends React.Component {
constructor(props) {
super(props);
this.state = { count: props.initialCount };
}
//元件即將載入
componentWillMount() {
//定義你的邏輯即可
console.log("Index - componentWillMount");
}
//元件載入完畢
componentDidMount() {
console.log("Index - componentDidMount");
}
render() {
/*
var component;
if (使用者已登入) {
component = <ComponentLoginedHeader/>
}
else{
component = <ComponentHeader/>
}
*/
return (
<div>
<ComponentHeader />
<BodyIndex />
<ComponentFooter />
</div>
);
}
}
ReactDOM.render(<BasicExample/>,document.getElementById('app'))
複製程式碼
事件處理
給事件處理函式傳遞額外引數的方式:bind(this, arg1, arg2, ...)
render: function() {
return <p onClick={this.handleClick.bind(this, 'extra param')}>;
},
handleClick: function(param, event) {
// handle click
}
複製程式碼
DOM操作
Refs 另外一種方式就是通過在要引用的 DOM 元素上面設定一個 ref 屬性指定一個名稱,然後通過 this.refs.name 來訪問對應的 DOM 元素。
比如有一種情況是必須直接操作 DOM 來實現的,你希望一個 元素在你清空它的值時 focus,你沒法僅僅靠 state 來實現這個功能。
class App extends Component {
constructor() {
return { userInput: '' };
}
handleChange(e) {
this.setState({ userInput: e.target.value });
}
clearAndFocusInput() {
this.setState({ userInput: '' }, () => {
this.refs.theInput.focus();
});
}
render() {
return (
<div>
<div onClick={this.clearAndFocusInput.bind(this)}>
Click to Focus and Reset
</div>
<input
ref="theInput"
value={this.state.userInput}
onChange={this.handleChange.bind(this)}
/>
</div>
);
}
}
複製程式碼
組合元件
使用元件的目的就是通過構建模組化的元件,相互組合元件最後組裝成一個複雜的應用。在 React 元件中要包含其他元件作為子元件,只需要把元件當作一個 DOM 元素引入就可以了。
var React = require('react');
var ReactDOM = require('react-dom');
import ComponentHeader from './components/ComponentHeader';
import ComponentFooter from './components/ComponentFooter';
import BodyIndex from './components/BodyIndex';
class Index extends React.Component {
//元件即將載入
componentWillMount() {
//定義你的邏輯即可
console.log("Index - componentWillMount");
}
//元件載入完畢
componentDidMount() {
console.log("Index - componentDidMount");
}
render() {
/*
var component;
if (使用者已登入) {
component = <ComponentLoginedHeader/>
}
else{
component = <ComponentHeader/>
}
*/
return (
<div>
<ComponentHeader />
<BodyIndex />
<ComponentFooter />
</div>
);
}
}
ReactDOM.render(
<Index />, document.getElementById('app'));
複製程式碼
元件間通訊
父子元件間通訊
-
1.父元件傳遞到子元件: 就是通過 props 屬性傳遞,在父元件給子元件設定 props,然後子元件就可以通過 props 訪問到父元件的資料/方法,這樣就搭建起了父子元件間通訊的橋樑。
-
2.父元件訪問子元件? 用 refs
非父子元件間的通訊 使用全域性事件 Pub/Sub 模式,在 componentDidMount 裡面訂閱事件,在 componentWillUnmount 裡面取消訂閱,當收到事件觸發的時候呼叫 setState 更新 UI。 一般來說,對於比較複雜的應用,推薦使用類似 Flux 這種單項資料流架構
使用css樣式
1.內聯樣式
在render函式裡定義
const styleComponentHeader = { header: { backgroundColor: '#333333', color: '#FFFFFF', 'padding-top': '12px', 'paddingBottom: '16px' } };
複製程式碼
注意樣式的駝峰寫法 style = {styleComponentHeader.header}
檔案中引用css的樣式 注意class需要更改成className確定是動畫、偽類(hover)等不能使用
2.內聯樣式中的表示式
paddingBottom:(this.state.minHeader)?"3px":"15px"
複製程式碼
注意好好理解這裡的state引起樣式的即時變化
3.CSS模組化
原因:避免全域性汙染、命名混亂、依賴管理不徹底、無法共享變數、程式碼壓縮不徹底
npm install --save-dev style-loader css-loader npm install --save-dev babel-plugin-react-html-attrs //為了使用原生的html屬性名
複製程式碼
全域性樣式和區域性樣式
:local(.normal){color:green;} //區域性樣式
:global(.btn){color:red;} //全域性樣式
複製程式碼
CSS模組化的優點 所有樣式都是local的,解決了命名衝突和全域性汙染問題 class名生成規則配置靈活,可以此來壓縮class名 只需引用元件的JS就能搞定元件所有的js和css 依然是css,幾乎零學習成本
jsx樣式與css的互轉 工具:staxmanade.com/CssToReact/
react-router
官網:reacttraining.com/react-route… GitHub:github.com/ReactTraini…
注意點: 1.引用的包是有區別的。
2.控制頁面的層級關係 單頁面構建Router控制 底層機制 React: state/props -> Components ->UI Router: location ->Router -> UI
3.router傳參 定義: path="list/:id" 使用: this.props.match.params.id
4.地址無法重新整理(巨坑) 表現:'/' 根節點 Home 顯示無誤,不過其他任何路由 例如 /detail,均顯示 Cannot GET /detail。 解決方法:
-
4.1 用的 BrowserRouter 改為 HashRouter 即可
-
4.2 修改 webpack.config.js 配置檔案
module.exports = {
// 省略其他的配置
devServer: {
historyApiFallback: true
}
}
複製程式碼
詳情可以參考:blog.csdn.net/zwkkkk1/art…
網路請求Fetch
fetch('/users.json')
.then(function(response) {
return response.json()
}).then(function(json) {
console.log('parsed json', json)
}).catch(function(ex) {
console.log('parsing failed', ex)
})
複製程式碼
Redux
下期再講
學習資料
練習程式碼 學習Demo樣例:github.com/xbmchina/re…
專案Demo樣例:github.com/xbmchina/re…
React相關資料
**React官網:reactjs.org/docs/hello-…
React中文網:react.docschina.org/
React學習文件:caibaojian.com/react/
webpack搭建React:segmentfault.com/a/119000001…
React-router官網:reacttraining.com/react-route…
阿里UI庫Ant Design:ant.design/index-cn
阿里圖示庫:www.iconfont.cn/
谷歌的ReactUI庫:material-ui.com/
css轉React:staxmanade.com/CssToReact/
Fetch請求:github.com/github/fetc…
最後
本人水平有限,歡迎各位建議以及指正。順便關注一下公眾號唄,會經常更新文章的哦。