學react是會有很多疑惑,
- 不知道需要用到ES6的哪些知識點;
- react component 有三種寫法,都要掌握還是
- ....
JavaScript語言
變量聲明
const 和 let
不要用var, 而是用const和let, 分別表示常量和變量。不同於var的函數作用域, const和let都是塊級作用域
const DELAY = 1000;
let count = 0;
count = count + 1;
模板字符串
模板字符串提供了另一種做字符串組合的方法。
const user = 'world';
console.log(`hello ${user}`); //hello world
//多行
const content = `
Hello ${firstName},
Thanks for ordering ${qty} tickets to ${event}.
`;
默認參數
function logActivity(activity = 'skiing') {
console.log(activity);
}
logAcitity(); //skiing
箭頭函數
函數的快捷寫法,不需要通過function關鍵字創建函數,並且還可以省略return關鍵字。同時,箭頭函數還會繼承當前四昂下文的this關鍵字。
比如:
[1, 2, 3].map(x => x + 1)
等同於:
[1, 2, 3].map((function(x) {
return x + 1;
}).bind(this));
模塊的import和export
import用於引入模塊,export用於導出模塊
比如:
//引入全部
import dva from 'dva';
//引入部分
import { connect } from 'dva';
import { Link, Route } from 'dva/router';
//引入全部並作爲github對象
import * as github from './service/github';
//導出默認
export default App;
//部分導出, 需import { App } from './file'; 引入
export class App extend Component {};
ES6對象和數組
析構賦值
析構賦值讓我們從Object或Array裏取出部分數據存爲變量
//對象
const user = { name: 'guanguan', age: 2};
const { name, age } = user;
console.log(`${name} : ${age} `); //guanguan : 2
//數組
const arr = [1, 2];
const [foo, bar] = arr;
console.log(foo);
我們也可以析構傳入的函數參數
const add = (state, { payload } => {
return state.concat(payload);
});
析構時還可以配alias(別名),讓代碼更具有語義
const add = (state, { payload: todo }) => {
return state.concat(todo);
};
對象字面量改進
這是析構的反向操作,用於重新組織一個object
const name = 'duoduo';
const age = 8;
const user = { name, age }; //{ name: 'duoduo', age: 8};
定義對象方法時,還可以省去function關鍵字
app.model({
reducers: {
add() {} //等同於 add: function() {}
},
effects: {
*addRemote() {} //等同於addRemote: function*() {}
},
});
Spread Operator
Spread Operator即3個點...,有幾種不同的使用方法
可用於組裝數組。
const name = 'duoduo';
const age = 8;
const user = { name, age }; //{ name: 'duoduo', age: 8};
也可用戶獲取數組的部分項
const arr = ['a', 'b', 'c'];
const [first, ...rest] = arr;
rest; //['b', 'c']
//with ignore
const [first, , ...rest] = arr;
rest; //['c']
代替apply
function foo(x, y, z){}
const args = [1, 2, 3];
//下面兩句效果相同
foo.apply(null, args);
foo(...args);
對於Object而言,用於組合成新的Object
const foo = {
a: 1,
b: 2,
};
const bar = {
b: 3,
c: 2
};
const d = 4;
const ret = {...foo, ...bar,d }; //{ a: 1, b: 3, c: 2, d: 4}
promise
promise用於更優雅地處理異步請求。比如發起異步請求:
fetch('/api/todos')
.then(res => res.json())
.then(data => ({ data }))
.catch(err => ({ err }));
定義Promise
const delay =(timeout) => {
return new Promise(resolve => {
setTimeout(resolve, timeout);
});
};
delay(1000).then(_ => {
console.log('executed');
});
Generators
dva的effects是通過generator組織的。generator返回的是迭代器,通過yield關鍵字實現暫停功能
這是一個典型的dva effect,通過yield把異步邏輯通過同步的方式組織起來
app.model({
namespace: 'todos',
effects: {
*addRemote({ payload: todo }, { put, call }) {
yield call(addTodo, todo);
yield put({ type: 'add', payload: todo });
},
},
});
React Component
Stateless Functional Component
react Compnent 有三種定義方式,分別是React.createClass, class和Stateless Functional Component。推薦使用最後一種,保持簡潔和無狀態,這是函數,不是Object,沒有this作用域。
比入定義App Component
function App(props){
function handleClick(){
props.dispatch({ type: 'app/create' })
}
return <div onClick={handleClick}>${props.name}</div>
}
JSX
Component嵌套
類似HTML,JSX裏可以給組件添加子組件
<App>
<Header />
<MainContent />
<Footer />
</App>
className
class是保留字,所以添加樣式時,需用className替代class
<h1 className = "fancy">hello dva</h1>
JavaScript表達式
javascript表達式需要用{}括起來,會執行並返回結果。
<h1>{this.props.title}</h1>
Mapping Arrays to JSX
可以把數組映射爲JSX元素列表
<ul>
{ this.props.todos.map((todo, i) => <li key={i}>{todo}</li>)}
</ul>
註釋
儘量別用“//”做單行註釋
<h1>
{/* multiline comment */}
{/*
multi
line
comment
*/}
{
//single line
}
Hello
</h1>
Spread Atrribute
這是JSX從ECMAScript6借鑑過來的很有用的特性,用於擴充組件props
別如:
const attrs = {
href: 'http://example.org',
target: '_blank',
};
<a {...attrs}>Hello</a>
等同於
const attrs = {
href: 'http://example.org',
target: '_blank',
}
<a href = {attrs.href} target = {target.target}>Hello</a>
Props
數據處理在React中是非常重要的概念之一,分別可以通過props,state和context來處理數據。而在dva中你只需要關心props
proptypes
JavaScript是弱類型語言,所以請儘量聲明propTypes對props進行校驗,以減少不必要的問題
function App(props) {
return <div>{props.name}</div>;
}
App.propTypes = {
name: React.PropTypes.string.isRequired,
};
內置的proptype有:
- PropTypes.array
- PropTypes.bool
- PropTypes.func
- PropTypes.number
- PropTypes.object
- PropTypes.string
往下傳數據:
往上傳數據:
CSS Modules
理解css Modules
一張圖理解CSS Modules的工作原理
buttom class在構建之後會被重命名爲ProductList_button_1FU0u.button是本地名稱。而 produc'tList_button_1FU0Ou是global name。你可以用簡短的描述性名字,而不需要關心命名衝突問題。
然後你要做的全部事情就是在css/less文件裏寫.button{...},並在組件裏通過style.button來引用他。
定義全局CSS
CSS Modules默認是局部作用域的,想要聲明一個全局規則,可用:global語法
.title {
color: red;
}
:global(.title) {
color: green;
}
然後在引用的時候:
<App className={styles.title} /> //red
<App className="title" /} //green
className Package
在一些複雜的場景中,一個元素可能對應多個classname,而每個className又基於一些條件來決定是否出現。這時,classname這個庫就非常有用。
import classnames from 'classnames';
const App = (props) => {
const cls = classnames({
btn: true,
btnLarge: props.type === 'submit',
btnSmall: props.type === 'edit',
});
return <div className = { cls }/>
};
這樣,傳入不同的type給App組件,就會返回不同的className組合:
<App type = "submit" /> //btn btnLarge
<App type = "edit" /} //btn btnSmall
Reducer
reducer是一個函數,接受state和action,返回老的或新的state。即:(state, action)=> state.
增刪改
以todos爲例。
app.model({
namespace: 'todos',
state: [],
reducer: {
add(state, { payload: todo}){
return state.concat(todo);
},
remove(state, { payload: id }) {
return state.filter(todo => todo.id != id)
},
update(state, { payload: updateTodo }) {
return state.map(todo => {
if(todo.id === updatedTodo.id) {
return {...todo, ...updatedTodo};
} else {
return todo;
}
});
},
},
});
深層嵌套的例子,應儘量避免。
Effect
app.model({
namespace: 'todos',
effects: {
*addRemote({ payload: todo },{ put, call }){
yield call(addTodo, todo);
yield put({ type: 'add', payload: todo });
},
},
});
effects
put
用於觸發action。
yield put({ type: 'todo/add', payload: 'Learn Dva' });
call
用於調用異步邏輯,支持promise
const result = yield call(fetch, '/todos');
select
用於從state裏獲取數據
const todos = yield select(state => state.todos);
錯誤處理
全局錯誤處理
dva裏,effects和subscriptions的拋錯全部會走onError hook,所以用在onError裏統一處理錯誤。
const app = dva({
onError(e, dispatch) {
console.log(e.message);
},
});
然後effects裏的拋錯和reject的promise就都會被捕獲到了。
本地錯誤處理
如果需要對某些effects的錯誤進行特殊處理,需要在effect內部加 try , catch
app.model({
effect: {
*addRemote() {
try{
//your code here
} catch(e) {
console.log(e.message);
}
},
},
});
異步請求
異步請求基於whatwg-fetch
GET和POST
import request from '../util/request';
//GET
request('/api/todos');
//POST
request('/api/todos', {
method: 'POST',
body: JSON.stringify({a: 1}),
});
統一錯誤處理
假如約定後臺返回一下格式時, 做統一的錯誤處理
{
status: 'error',
message: '',
}
編輯utils/request.js,加入以下中間件。
function parseErrorMessage({ data }) {
const { status, message } = data;
if(status === 'error' ){
throw new Error(message);
}
return { data };
}
然後,這類錯誤就會走到onError hook裏。
Subscription
subscription是訂閱,用於訂閱一個數據源,然後根據需要dispatch相應的action。數據源是可以是當前的時間/服務器的websocket連接,keyboard輸入,geolocation變化,history路由變化等。格式爲({ dispatch, history })=> unsubscribe
異步數據初始化
比如:當用戶進入/users頁面時,觸發action user/fetch加載用戶數據。
app.model({
subscriptions: {
setup({ dispatch, history })=>{
history.listen(({ pathname }) => {
if(pathname === '/users') {
dispatch({
type: 'users/fetch',
});
}
});
},
},
});
path-to-regexp Package
如果url規則比較複雜,比如/users/:userId/search,那麼匹配和userId的獲取都會比較麻煩這裏推薦用path-to-regexp簡化這部分邏輯
import pathToRegexp from 'path-to-regexp';
// in subscription
const match = pathToRegexp('/users/:userId/search').exec(pathname);
if (match) {
const userId = match[1];
// dispatch action with userId
}
Router
config with JSX Element (router.js)
<Route path = "/" component = {App} />
<Route path = "accounts" component = {Accounts} />
<Route path = "statements" compnent = {Statements} />
</Route>
Router Components
Route Components 是指 ./src/routes/目錄下的文件,他們是./src/router.js裏匹配
通過connect綁定數據
import { connect } from 'dva';
function App() {}
function mapStateToProps(state, ownProps) {
return {
users: state.users,
};
}
export default connect(mapStateToProps)(App);
然後在App裏就有了dispatch和users兩個屬性
injected Props(e.g.location)
Route Compotent 會有額外的props用以獲得路由信息
- location
- params
基於action進行頁面跳轉
import { routerRedux } from 'dva/router';
//Inside Effects
yield put(routerRedux.push('/logout'));
//Out Effects
dispatch(routerRedux.push('/logout'));
//with query
routerRedux.push({
pathname: '/logout',
query: {
page: 2,
},
});
除push(location) 外還有更多方法
dva配置
Redux Middleware
比如要添加redux-logger中間件:
import createLogger from 'redux-logger';
const app = dva({
onAction: createLogger(),
});
注:onAction支持數組,可同時傳入多箇中間件
history
切換history爲browserHistory
import { browserHistory } from 'dva/router';
const app = dva({
history: browserHistory,
});
去除hashHistory下的_k查詢參數
import { useRouterHistory } from 'dva/router';
import { createHashHistory } from 'history';
const app = dva({
history: userRouterHistory(createHashHistory)({querykey: false}),
});
工具
通過dva-cli創建項目
先安裝dva-cli
$ npm install dva-cli -g
然後創建項目
$ dva new myapp
最後,進入目錄並啓動
$ cd myapp
$ npm start