学react是会有很多疑惑,
变量声明
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 });
},
},
});
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 ${props.name}
}
Component嵌套
类似HTML,JSX里可以给组件添加子组件
className
class是保留字,所以添加样式时,需用className替代class
hello dva
JavaScript表达式
javascript表达式需要用{}括起来,会执行并返回结果。
{this.props.title}
Mapping Arrays to JSX
可以把数组映射为JSX元素列表
{ this.props.todos.map((todo, i) => - {todo}
)}
注释
尽量别用“//”做单行注释
{/* multiline comment */}
{/*
multi
line
comment
*/}
{
//single line
}
Hello
Spread Atrribute
这是JSX从ECMAScript6借鉴过来的很有用的特性,用于扩充组件props
别如:
const attrs = {
href: 'http://example.org',
target: '_blank',
};
Hello
等同于
const attrs = {
href: 'http://example.org',
target: '_blank',
}
Hello
Props
数据处理在React中是非常重要的概念之一,分别可以通过props,state和context来处理数据。而在dva中你只需要关心props
proptypes
JavaScript是弱类型语言,所以请尽量声明propTypes对props进行校验,以减少不必要的问题
function App(props) {
return {props.name};
}
App.propTypes = {
name: React.PropTypes.string.isRequired,
};
内置的proptype有:
往下传数据:
往上传数据:
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;
}
然后在引用的时候:
//red
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
};
这样,传入不同的type给App组件,就会返回不同的className组合:
//btn btnLarge
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
}
config with JSX Element (router.js)
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用以获得路由信息
基于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) 外还有更多方法
Redux Middleware
比如要添加redux-logger中间件:
import createLogger from 'redux-logger';
const app = dva({
onAction: createLogger(),
});
注:onAction支持数组,可同时传入多个中间件
切换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