本节内容
课堂目标
redux
资源
起步
redux快速上手
1.安装
npm i redux -S
2.redux中的角色
- Store
- 维持应用的 state;
- 提供
getState()
方法获取 state; - 提供
dispatch(action)
方法更新 state; - 通过
subscribe(listener)
注册监听器; - 通过
subscribe(listener)
返回的函数注销监听器。
- Reducer:指定了应用状态的变化如何响应 actions 并发送到 store 的
- Action:把数据从应用传到store的有效载荷
store.js
import {
createStore
} from 'redux';
// 创建reducer 状态修改具体执行者
function counter(state = 0, action) {
switch (action.type) {
case 'INCREMENT':
return state + 1;
case 'DRCREMENT':
return state - 1;
default:
return state;
}
}
//创建store并导出
export default createStore(counter);
ReduxTest.js
import React, { Component } from 'react';
import store from '../store';
class ReduxTest extends Component {
render() {
return (
<div>
<p>
{store.getState()}
</p>
<button onClick={() => store.dispatch({ type:"DRCREMENT"})}>-1</button>
<button onClick={() => store.dispatch({ type: "INCREMENT" })}>+1</button>
</div>
);
}
}
export default ReduxTest;
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import ReduxTest from './components/ReduxTest';
import store from './store'
function render() {
ReactDOM.render(<ReduxTest />, document.querySelector('#root'));
}
render();
// 每次 state 更新时,打印日志
// 注意 subscribe() 返回一个函数用来注销监听器
// 订阅
store.subscribe(render)
Redux架构的设计核心:严格的单向数据流
问题:每次state更新,都会重新render,大型应用中会造成不必要的重复渲染。
如何更优雅的使用redux呢?react-redux
npm i react-redux -S
具体步骤:
- React Redux提供了
<Provider />
,使得Redux store都应用到你的应用程序
修改index.js
import React from 'react';
import ReactDOM from 'react-dom';
import ReduxTest from './components/ReduxTest';
import store from './store'
import { Provider } from 'react-redux';
function render() {
ReactDOM.render((
<Provider store = {store}>
<ReduxTest />
</Provider>
), document.querySelector('#root'));
}
render();
//订阅不需要了
// store.subscribe(render);
React Redux提供了connect
将组件连接到store的功能
修改ReduxTest.js
import React, { Component } from 'react';
import { connect } from "react-redux";
const mapStateToProps = state => {
return {
num: state
}
}
const mapDispatchToProps = dispatch => {
return {
increment: () => {
dispatch({ type: 'INCREMENT' })
},
decrement: () => {
dispatch({
type: 'DRCREMENT'
})
}
}
}
class ReduxTest extends Component {
render() {
return (
<div>
<p>{this.props.num}</p>
<button onClick={() => this.props.decrement()}>-1</button>
<button onClick={() => this.props.increment()}>+1</button>
</div>
);
}
}
export default connect(mapStateToProps, mapDispatchToProps)(ReduxTest);;
装饰器写法
mport React, { Component } from 'react';
import { connect } from "react-redux";
const mapStateToProps = state => {
return {
num: state
}
}
const mapDispatchToProps = dispatch => {
return {
increment: () => {
dispatch({ type: 'INCREMENT' })
},
decrement: () => {
dispatch({
type: 'DRCREMENT'
})
}
}
}
@connect(mapStateToProps, mapDispatchToProps)
class ReduxTest extends Component {
render() {
return (
<div>
<p>{this.props.num}</p>
<button onClick={() => this.props.decrement()}>-1</button>
<button onClick={() => this.props.increment()}>+1</button>
</div>
);
}
}
export default ReduxTest;
容器组件就是使用 store.subscribe()
从 Redux state 树中读取部分数据,并通过 props 来把这些数据提供给要渲染的组件。你可以手动来开发容器组件,但建议使用 React Redux 库的 connect()
方法来生成,这个方法做了性能优化来避免很多不必要的重复渲染。
使用connect()
前,需要先定义mapStateToProps
这个函数来指定如何把当前的Redux store state映射到展示组件的props中。
redux中间件
利用redux中间件机制可以在实际action响应前执行其它额外的业务逻辑。
特点:自由组合,自由插拔的插件机制
通常我们没有必要自己写中间件,介绍两款比较成熟的中间件
- redux-logger:处理日志记录的中间件
- Redux-thunk:处理异步action
npm i redux-thunk redux-logger -S
redux-logger的使用在store.js加入
import {
createStore,
applyMiddleware
} from 'redux';
import logger from 'redux-logger';
// 创建reducer
function counter(state = 0, action) {
switch (action.type) {
case 'INCREMENT':
return state + 1;
case 'DRCREMENT':
return state - 1;
default:
return state;
}
}
export default createStore(counter, applyMiddleware(logger));
效果:
redux-thunk 在store.js修改
import {
createStore,
applyMiddleware
} from 'redux';
import logger from 'redux-logger';
import thunk from 'redux-thunk';
// 创建reducer
function counter(state = 0, action) {
switch (action.type) {
case 'INCREMENT':
return state + 1;
case 'DRCREMENT':
return state - 1;
default:
return state;
}
}
export default createStore(counter, applyMiddleware(logger,thunk));
添加thunk的作用:action默认接收一个对象,执行下个任务,如果是个函数,则需要异步处理。
redux-thunk 在ReduxTest.js修改
import React, { Component } from 'react';
// import store from '../store';
import { connect } from "react-redux";
const mapStateToProps = state => {
return {
num: state
}
}
const asyncAdd = () => {
return (dispatch,getState)=>{
setTimeout(() => {
dispatch({type:'INCREMENT'})
}, 1000);
}
}
const mapDispatchToProps = (dispatch) => {
return {
increment: () => {
dispatch({ type: 'INCREMENT' });
},
decrement: () => {
dispatch({
type: 'DRCREMENT'
})
},
asyncIncrement: () => {
//action的动作默认是对象,如果是返回函数则使用redux-thunk处理
dispatch(asyncAdd());
}
}
}
@connect(mapStateToProps, mapDispatchToProps)
class ReduxTest extends Component {
render() {
return (
<div>
<p>{this.props.num}</p>
<button onClick={() => this.props.decrement()}>-1</button>
<button onClick={() => this.props.increment()}>+1</button>
<button onClick={() => this.props.asyncIncrement()}>async+1</button>
</div>
);
}
}
export default ReduxTest;
效果展示:
重构项目
新建store/couter.reduce.js
// 创建reducer
const counter = (state = 0, action) => {
switch (action.type) {
case 'INCREMENT':
return state + 1;
case 'DRCREMENT':
return state - 1;
default:
return state;
}
}
export const mapStateToProps = state => {
return {
num: state
}
}
const asyncAdd = () => {
return (dispatch, getState) => {
setTimeout(() => {
dispatch({ type: 'INCREMENT' })
}, 1000);
}
}
export const mapDispatchToProps = (dispatch) => {
return {
increment: () => {
// dispatch({ type: 'INCREMENT' })
dispatch({ type: 'INCREMENT' });
},
decrement: () => {
dispatch({
type: 'DRCREMENT'
})
},
asyncIncrement: () => {
dispatch(asyncAdd());
}
}
}
export default counter;
新建store/index.js
import {
createStore,
applyMiddleware
} from 'redux';
import logger from 'redux-logger';
import thunk from 'redux-thunk';
import counter from './couter.reducer';
export default createStore(counter, applyMiddleware(logger,thunk));
重构ReduxTest.js
import React, { Component } from 'react';
// import store from '../store';
import { connect } from "react-redux";
import {mapStateToProps,mapDispatchToProps} from '../store/couter.reducer';
@connect(mapStateToProps, mapDispatchToProps)
class ReduxTest extends Component {
render() {
return (
<div>
<p>{this.props.num}</p>
<button onClick={() => this.props.decrement()}>-1</button>
<button onClick={() => this.props.increment()}>+1</button>
<button onClick={() => this.props.asyncIncrement()}>async+1</button>
</div>
);
}
}
export default ReduxTest;
合并reducer
使用combineReducers
进行复合,实现状态的模块化
import {
createStore,
applyMiddleware,
combineReducers
} from 'redux';
import logger from 'redux-logger';
import thunk from 'redux-thunk';
import counter from './couter.reducer';
export default createStore(
combineReducers({
counter
}), applyMiddleware(logger,thunk));
counter.reducer.js
export const mapStateToProps = state => {
return {
//加上当前状态的key,来进行标识
num: state.counter
}
}
Mobx快速入门
React 和 MobX 是一对强力组合。
React是一个消费者,将应用状态state渲染成组件树对其渲染。
Mobx是一个提供者,用于存储和更新状态state
下载
npm i mobx mobx-react -S
新建store/mobx.js
import { observable,action,computed} from "mobx";
// 观察者
const appState = observable({
num: 0
})
// 方法
appState.increment = action(()=>{
appState.num+=1;
})
appState.decrement = action(()=>{
appState.num-=1;
})
export default appState;
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import {Provider} from 'react-redux';
import MobxTest from "./components/MobxTest";
ReactDOM.render((
<div>
<MobxTest appState = {appState}/>
</div>
), document.querySelector('#root'));
MobxTest.js
import React, { Component } from 'react';
import { observer } from "mobx-react";
class MobxTest extends Component {
render() {
return (
<div>
{this.props.appState.num}
<button onClick={() => this.props.appState.decrement()}>-1</button>
<button onClick={() => this.props.appState.increment()}>+1</button>
</div>
);
}
}
export default observer(MobxTest);
装饰器写法:
store/mobx.decorator.js
import { observable, action, computed } from "mobx";
// 常量改成类
class AppState {
@observable num = 0;
@action
increment(){
this.num +=1;
}
@action
decrement() {
this.num -= 1;
}
}
const appState = new AppState();
export default appState;
MobxTest.decorator.js
import React, { Component } from 'react';
import { observer } from "mobx-react";
@observer
class MobxTest extends Component {
render() {
return (
<div>
{this.props.appState.num}
<button onClick={() => this.props.appState.decrement()}>-1</button>
<button onClick={() => this.props.appState.increment()}>+1</button>
</div>
);
}
}
export default MobxTest;
对比react和Mobx
- 学习难度
- 工作量
- 内存开销
- 状态管理的集中性
- 样板代码的必要性
- 结论:使用Mobx入门简单,构建应用迅速,但是当项目足够大的时候,还是redux,爱不释手,那还是开启严格模式,再加上一套状态管理的规范。爽的一p
react-router4.0
资源
快速入门
安装
npm install react-router-dom --save
基本路由使用
import React, { Component } from 'react';
import { BrowserRouter as Router, Route, Link } from "react-router-dom";
function Home() {
return (
<h2>我是首页</h2>
)
}
function Course() {
return (
<h2>我是课程</h2>
)
}
function User() {
return (
<h2>我是首页</h2>
)
}
class Basic_router extends Component {
render() {
return (
<Router>
<div>
{/* 定义路由页面 */}
<ul>
<li>
<Link to='/'>首页</Link>
</li>
<li>
<Link to='/course'>课程</Link>
</li>
<li>
<Link to='/user'>用户</Link>
</li>
</ul>
{/* 配置路由 */}
{/* 为什么要加exact 这是因为包含式匹配,加上exact之后,表示确切匹配 */}
<Route exact path='/' component={Home}></Route>
<Route path='/course' component={Course}></Route>
<Route path='/user' component={User}></Route>
</div>
</Router>
);
}
}
export default Basic_router;
路由URL参数(二级路由)
import React, { Component } from 'react';
import { BrowserRouter as Router, Route, Link } from "react-router-dom";
function Home() {
return (
<h2>我是首页</h2>
)
}
function Course() {
return (
<div className='course'>
<h2>我的课程</h2>
{/*定义二级路由页面*/}
<ul>
<li>
<Link to='/course/vue'>Vue</Link>
</li>
<li>
<Link to='/course/React'>React</Link>
</li>
<li>
<Link to='/course/Angular'>Angular</Link>
</li>
</ul>
{/*配置路由参数*/}
<Route path='/course/:id' component={CourseChild}></Route>
<Route exact path={match.path} render={() => <h3>请选择你的课程</h3>} />
</div>
)
}
function User() {
return (
<h2>我是首页</h2>
)
}
//二级路由页面显示
function CourseChild({match,history,location}) {
//match: 匹配路由信息对象
//location: 本地信息对象
//history: 历史信息对象
console.log(location,match,history);
return (
<div>
{match.params.id}
</div>
)
}
class Basic_router extends Component {
render() {
return (
<Router>
<div>
{/* 定义路由页面 */}
<ul>
<li>
<Link to='/'>首页</Link>
</li>
<li>
<Link to='/course'>课程</Link>
</li>
<li>
<Link to='/user'>用户</Link>
</li>
</ul>
{/* 配置路由 */}
<Route exact path='/' component={Home}></Route>
<Route path='/course' component={Course}></Route>
<Route path='/user' component={User}></Route>
</div>
</Router>
);
}
}
export default Basic_router;
上述的Course组件也可以这样修改
function Course({match}) {
return (
<div className='course'>
<h2>我的课程</h2>
<ul>
<li>
<Link to={`${match.url}/vue`}>Vue</Link>
</li>
<li>
<Link to={`${match.url}/react`}>React</Link>
</li>
<li>
<Link to={`${match.url}/angular`}>Angular</Link>
</li>
</ul>
<Route path='/course/:id' component={CourseChild}></Route>
<Route exact path={match.path} render={() => <h3>请选择你的课程</h3>} />
</div>
)
}
不匹配(404)
// 404页面展示
function NoMatch() {
return <div>404页面,网页找不到了</div>
}
class Basic_router extends Component {
render() {
return (
<Router>
<div>
{/* 定义路由页面 */}
<ul>
<li>
<Link to='/'>首页</Link>
</li>
<li>
<Link to='/course'>课程</Link>
</li>
<li>
<Link to='/user'>用户</Link>
</li>
</ul>
{/* 配置路由 */}
<Route exact path='/' component={Home}></Route>
<Route path='/course' component={Course}></Route>
<Route path='/user' component={User}></Route>
{/*添加不匹配路由配置*/}
<Route component={NoMatch}></Route>
</div>
</Router>
);
}
}
此时会发现,每个页面都会匹配NoMatch组件,这时候该是Switch
组件出厂了
修改以上代码如下
import React, { Component } from 'react';
import { BrowserRouter as Router, Route, Link,Switch } from "react-router-dom";
// 404页面展示
function NoMatch() {
return <div>404页面,网页找不到了</div>
}
class Basic_router extends Component {
render() {
return (
<Router>
<div>
{/* 定义路由页面 */}
<ul>
<li>
<Link to='/'>首页</Link>
</li>
<li>
<Link to='/course'>课程</Link>
</li>
<li>
<Link to='/user'>用户</Link>
</li>
</ul>
{/* 配置路由 */}
<Switch>
<Route exact path='/' component={Home}></Route>
<Route path='/course' component={Course}></Route>
<Route path='/user' component={User}></Route>
{/*添加不匹配路由配置*/}
<Route component={NoMatch}></Route>
</Switch>
</div>
</Router>
);
}
}
命令式导航
function Home({ location }) {
console.log(location);
return (
<div>
<h1>{location.state ? location.state.foo : ""}</h1>
<h2>我是首页</h2>
</div>
)
}
function CourseChild({ match, history, location }) {
return (
<div>
{match.params.id}课程
<button onClick={history.goBack}>返回</button>
<button onClick={() => { history.push('/') }}>跳转首页</button>
<button onClick={()=>{
history.push({
pathname:'/',
state:{
foo:'bar'
}
})
}}>跳转首页,并携带值</button>
</div>
)
}
重定向Redirect
import React, { Component } from 'react';
import { BrowserRouter as Router, Route, Link, Switch, Redirect} from "react-router-dom";
function UserDeatil({match,location}) {
return (
<div>个人详情页面</div>
)
}
function UserOrder(params) {
return (
<div>用户订单页面</div>
)
}
function User() {
return (
<div>
<h2>
<Link to='/user/detail'>个人信息</Link>
</h2>
<h2>
<Link to='/user/order'>个人订单</Link>
</h2>
<Switch>
<Route path="/user/detail" component={UserDeatil}></Route>
<Route path="/user/order" component={UserOrder}></Route>
{/*重定向*/}
<Redirect to='/user/detail'></Redirect>
</Switch>
</div>
)
}
class Basic_router extends Component {
render() {
return (
<Router>
<div>
{/* 定义路由页面 */}
<ul>
<li>
<Link to='/user'>用户</Link>
</li>
</ul>
{/* 配置路由 */}
<Switch>
<Route path='/user' component={User}></Route>
</Switch>
</div>
</Router>
);
}
}
export default Basic_router;
路由守卫
定义可以验证的高阶组件
// 路由守卫:定义可以验证的高阶组件
function PrivateRoute({ component: Component, ...rest }) {
return (
<Route
{...rest}
render={props =>
Auth.isAuth ? (
<Component {...props} />
) : (
<Redirect to={{
pathname: "/login",
state: { from: props.location }
}}
/>
)
}
/>
)
}
认证类Auth
const Auth = {
isAuth: false,
login(cb) {
this.isAuth = true;
setTimeout(cb, 1000);
},
signout(cb) {
this.isAuth = false;
setTimeout(cb, 1000);
}
}
定义登录组件
class Login extends Component {
state = { isLogin: false };
handlerlogin = () => {
Auth.login(() => {
this.setState({
isLogin:true
})
})
}
render() {
let { isLogin } = this.state;
let { from } = this.props.location.state || { from: { pathname: '/' } }
if (isLogin) return <Redirect to={from} />
return (
<div>
<p>请先登录</p>
<button onClick={this.handlerlogin}>登录</button>
</div>
);
}
}
主路由组件中使用自定义路由和定义登录路由配置
<Switch>
<Route exact path='/' component={Home}></Route>
{/* <Route path='/course' component={Course}></Route> */}
<PrivateRoute path='/course' component={Course}></PrivateRoute>
<Route path='/user' component={User}></Route>
<Route path='/login' component={Login}></Route>
<Route component={NoMatch}></Route>
</Switch>
集成到redux中
-
新建/store/user.reducer.js
const initState = { isLogin: false,//表示用户未登录 userInfo: {} } function user(state = initState, action) { switch (action.type) { case 'login': return { isLogin: true } default: return initState } } export const mapStateToProps = state => { return { // 加上当前状态的key,来进行模块化的标识 user: state.user } } const login = () => { return (dispatch) => { setTimeout(() => { dispatch({ type: 'login' }) }, 1000); } } export const mapDispatchToProps = dispatch => { return { //action 默认接收一个对象,执行下个任务,如果是一个函数,则需要异步处理,react-thunk login: () => { dispatch(login()) } } } export default user
新建store/index.js
// combineReducers 进行复合,实现状态的模块化 import { createStore, applyMiddleware, combineReducers } from "redux"; import logger from "redux-logger"; import thunk from "redux-thunk"; import user from './user.reducer' // 创建store 有state和reducer的store const store = createStore(combineReducers({ user }), applyMiddleware(logger, thunk)); export default store;
在index.js
import React from 'react'; import ReactDOM from 'react-dom'; import './index.css'; import App from './App'; import { Provider } from "react-redux"; import store from './store/index' ReactDOM.render(( <Provider store={store}> <App /> </Provider> ), document.getElementById('root'));
App.js修改
.... import { connect } from "react-redux"; import { mapStateToProps } from "./store/user.reducer"; // 高阶组件:定义验证功能的路由组件 @connect(mapStateToProps) class PrivateRoute extends Component { render() { const Comp = this.props.component; return ( <Route {...this.props} component={ (props) => this.props.user.isLogin ? (<Comp {...props} />) : (<Redirect to={{ pathname: '/login', state: { from: props.location } }} />) }> </Route> ) } } ....
login.js组件修改
import React, { Component } from 'react' import Auth from '../utils/auth'; import { Button } from "antd"; import { Redirect } from "react-router-dom"; import { connect } from "react-redux"; import { mapStateToProps, mapDispatchToProps } from '../store/user.reducer'; @connect(mapStateToProps,mapDispatchToProps) class Login extends Component { handleLogin = () => { // 异步处理 this.props.login(); } render() { let { isLogin } = this.props.user; let path = this.props.location.state.from.pathname if (isLogin) { return <Redirect to={path}/> } else { return ( <div> <p>请先登录</p> <Button onClick={this.handleLogin}>登录</Button> </div> ) } } } export default Login
redux原理
createStore
是一个函数,接收三个参数reducer,preloadedState,enhancer
enhancer
是一个高阶函数,用于增强create出来的store,他的参数是createStore
,返回一个更强大的store生成函数。(功能类似于middleware)。- 我们mobile仓库中的
storeCreator
其实就可以看成是一个enhancer,在createStore的时候将saga揉入了进去只不过不是作为createStore的第三个参数完成,而是使用middleware
完成。
export default function createStore(reducer,preloadedState,enchancer) {
if (typeof preloadedState === 'function' && typeof enhancer === 'function' || typeof enhancer === 'function' && typeof arguments[3] === 'function') {
throw new Error('It looks like you are passing several store enhancers to ' + 'createStore(). This is not supported. Instead, compose them ' + 'together to a single function.');
}
//如果传递了第二个参数preloadedState,而且第二个参数不是一个function , 则将preloadedState 保存在内部变量currentState中, 也就是我们给State 的默认状态
if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
enhancer = preloadedState;
preloadedState = undefined;
}
if (typeof enhancer !== 'undefined') {
if (typeof enhancer !== 'function') {
throw new Error('Expected the enhancer to be a function.');
}
// createStore 作为enhancer的参数,返回一个被加强的createStore,然后再将reducer, preloadedState传进去生成store
return enhancer(createStore)(reducer, preloadedState);
}
//第一个参数reducer 是必须要传递的而且必须是一个函数,不然Redux会报错
if (typeof reducer !== 'function') {
throw new Error('Expected the reducer to be a function.');
}
//仓库内部保存了一颗状态树。可以是任意类型
let currentState = preloadedState;
let currentListeners=[];
let currentReducer = reducer
function getState() {
return JSON.parse(JSON.stringify(state));
}
//组件可以派发动作给仓库
function dispatch(action) {
//调用reducer进行处理,获取老的state,计算出新的state
currentState=currentReducer(currentState,action);
//通知其他的组件执行
currentListeners.forEach(l=>l());
}
//如果说其他的组件需要订阅状态变化时间的话,
function subscribe(listener) {
//将监听函数放入一个队列中
currentListeners.push(listener);
return function () {
currentListeners = currentListeners.filter(item=>item!==listener);
}
}
//初始化的操作
dispatch({type:'@@INIT'});
return {
getState,
dispatch,
subscribe
}
}
- applyMiddleware与enhancer关系
- 首先他们两个的功能一样,都是为了增强store
- applyMiddleware的结果,其实一个enhancer
export function applyMiddleware(...middlewares){
return (createStore) => {
return function (...args) {
//创建原始的store
const store = createStore(...args);
//获取原始的dispatch
const _dispatch = store.dispatch;
const middlewareAPI = {
getState: store.getState,
dispatch: (...args)=> {
return dispatch(...args)
}
};
//调用第一层中间件
const middlewareChain = middlewares.map( (middleware)=> {
//让每个中间件执行,传入一个对象{getState,dispatch}
return middleware(middlewareAPI);
});
//通过compose复合函数,先将当前中间件的事情做完,然后继续调用下一个中间件,并且将值(store.dispatch)传入,增强dispatch
_dispatch = compose(...middlewareChain)(store.dispatch);
// return 一个被增强了dispatch的store
return {
...store,
dispatch: _dispatch
};
};
};
}
function compose(...fns){ //[add1,add2,add3] 都是函数
if(fns.length === 0){
return arg => arg;
}
if(fn2.length === 1){
return fns[0]
}
return fns.reduce((f1,f2)=>(...args)=> f1(f2(...args)))
}
React-redux原理
import React,{Component} from 'react';
import {bindActionCreators} from '../redux';
/**
* connect实现的是仓库和组件的连接
* mapStateToProps 是一个函数 把状态映射为一个属性对象
* mapDispatchToProps 也是一个函数 把dispatch方法映射为一个属性对象
*/
export default function connect(mapStateToProps,mapDispatchToProps) {
return function (Com) {
//在这个组件里实现仓库和组件的连接
class Proxy extends Component{
state=mapStateToProps(this.props.store.getState())
componentDidMount() {
//更新状态
this.unsubscribe = this.props.store.subscribe(() => {
this.setState(mapStateToProps(this.props.store.getState()));
});
}
componentWillUnmount = () => {
this.unsubscribe();
}
render() {
let actions={};
//如果说mapDispatchToProps是一个函数,执行后得到属性对象
if (typeof mapDispatchToProps === 'function') {
actions = mapDispatchToProps(this.props.store.dispatch);
//如果说mapDispatchToProps是一个对象的话,我们需要手工绑定
} else {
actions=bindActionCreators(mapDispatchToProps,this.props.store.dispatch);
}
return <Com {...this.state} {...actions}/>
}
}
}
export default class Provider extends Component{
//规定如果有人想使用这个组件,必须提供一个redux仓库属性
static propTypes={
store:PropTypes.object.isRequired
}
render() {
let value={store:this.props.store};
return (
<StoreProvider value={value}>
{this.props.children}
</StoreProvider>
)
}
}
redux-thunk
const thunk = ({dispatch,getState})=>next=>action=>{
if(typeof action=='function'){
return action(dispatch,getState)
}
return next(action)
}
export default thunk;
redux-saga完美方案
redux-saga
是一个用于管理应用程序 Side Effect(副作用,例如异步获取数据,访问浏览器缓存等)的 library,它的目标是让副作用管理更容易,执行更高效,测试更简单,在处理故障时更容易。
redux-saga 使用了 ES6 的 Generator 功能,让异步的流程更易于读取,写入和测试。
通过这样的方式,这些异步的流程看起来就像是标准同步的 Javascript 代码。
不同于 redux thunk,你不会再遇到回调地狱了,你可以很容易地测试异步流程并保持你的 action 是干净的。
安装
npm install redux-saga --save
新建store/sagas.js
import { call, put, takeEvery } from "redux-saga/effects";
// 模拟登录的api 一般项目开发中会将此api放入service文件夹下
const api = {
login(){
return new Promise((resolve, reject) => {
setTimeout(() => {
if(Math.random() > 0.5){
resolve({id:1,name:"Tom"})
}else{
reject('用户名或密码错误')
}
}, 1000);
})
}
}
// worker saga :将login action被dispacth时调用
function* login(action) {
try {
const result = yield call(api.login);
yield put({ type: 'login', result });
} catch (error) {
yield put({ type: 'loginError', message: error.message });
}
}
// 类似监听器
function* mySaga() {
yield takeEvery('login_request',login);
}
export default mySaga;
为了跑起Saga,我们需要使用redux-saga
中间件将Saga与Redux Store建立连接。
修改store/index.js
import {
createStore,
applyMiddleware,
combineReducers
} from 'redux';
import logger from 'redux-logger';
// 注册reducer
import user from './user.reducer';
import createSagaMiddleware from 'redux-saga'
import mySaga from './sagas';
// 1.创建中间件
const mid = createSagaMiddleware();
// createSagaMiddleware是一个工厂函数,传入helloSaga参数之后会创建一个saga middleware
// 使用applyMiddleware将middleware连接到store
//2.应用中间件
const store = createStore(
combineReducers({
user
})
, applyMiddleware(logger,mid));
//3.运行中间件
mid.run(mySaga)
export default store;
修改user.reducer.js
// 定义user的reducer
const initialState = {
isLogin: false,//一开始表示没登录
}
export default (state = initialState, { type, payload }) => {
switch (type) {
case 'login':
// return Object.assign({}, state, {
// isLogin: true
// })
return { ...state, ...{ isLogin: true} };
// return {isLogin:true}
default:
return state
}
}
export const mapStateToProps = state => {
const {isLogin} = state.user;
return {
isLogin: isLogin
}
}
export const mapDispatchToProps = (dispatch) => {
return {
login: () => {
dispatch(asyncLogin());
}
}
}
// 异步方法 for redux-thunk
/* function asyncLogin() {
return (dispatch) => {
setTimeout(() => {
dispatch({ type: 'login' })
}, 1250);
}
} */
// for redux-saga
function asyncLogin() {
alert(1);
return {type:'login_request'}
}
redux-thunk和redux-saga的区别
thunk可以接受function类型的action,saga则是纯对象action解决方案
saga使用generator解决异步问题,非常容易用同步方式编写异步代码
UmiJS
它是一个可插拔的企业级的react应用框架。umi以路由在基础并配以完善的插件体系。覆盖从源码到构建产物的每个生命周期,支持各种功能扩展和业务需求,目前内外部加起来已有 50+ 的插件。
umi 是蚂蚁金服的底层前端框架,已直接或间接地服务了 600+ 应用,包括 java、node、H5 无线、离线(Hybrid)应用、纯前端 assets 应用、CMS 应用等。他已经很好地服务了我们的内部用户,同时希望他也能服务好外部用户。
特性
- 📦 开箱即用,内置 react、react-router 等
- 🏈 类 next.js 且功能完备的路由约定,同时支持配置的路由方式
- 🎉 完善的插件体系,覆盖从源码到构建产物的每个生命周期
- 🚀 高性能,通过插件支持 PWA、以路由为单元的 code splitting 等
- 💈 支持静态页面导出,适配各种环境,比如中台业务、无线业务、egg、支付宝钱包、云凤蝶等
- 🚄 开发启动快,支持一键开启 dll 等
- 🐠 一键兼容到 IE9,基于 umi-plugin-polyfills
- 🍁 完善的 TypeScript 支持,包括 d.ts 定义和 umi test
- 🌴 与 dva 数据流的深入融合,支持 duck directory、model 的自动加载、code splitting 等等
快速上手
npm i yarn tyarn -g
# 以后所有的yarn 改成tyarn下载
# 全局安装umi,保证版本是2.0.0以上
yarn global add umi
脚手架
找个空地方新建空目录
mkdir umi_app && cd umi_app
然后通过umi g
创建一些页面
umi g page index
执行命令tree
,查看目录结构
└── pages
├── index.css
├── index.js
然后启动本地服务器
umi dev
页面中跳转
路由
umi会根据pages
目录自动生成路由配置
基础路由
此操作在上面演示完成
动态路由
umi里约定,带$
前缀的目录或文件为动态路由
目录结构如下:
└── pages
├── index.css
├── index.js
└── users
├── $id.css
├── $id.js
路由配置如下:
{
path: '/users/:id',
exact: true,
component: require('../users/$id.js').default,
}
修改$id.js
// 约定式路由
import styles from './$id.css';
export default function ({match}) {
return (
<div className={styles.normal}>
<h1>user index {match.params.id}</h1>
</div>
);
}
当访问localhost:8000/users/1
和localhost:8000/user/2
来查看效果
嵌套路由
umi里约定目录下有_layout.js
时会生成嵌套路由,以_layout.js
为该目录的layout
umi g users/_layout
umi g users/index
生成如下目录结构
└── pages
├── index.css
├── index.js
└── users
├── $id.css
├── $id.js
├── _layout.css
├── _layout.js
路由配置如下:
{
path: '/users',
exact: false,
component: require('../users/_layout.js').default,
routes: [
{
path: '/users',
exact: true,
component: require('../users/index.js').default,
},
{
path: '/users/:id',
exact: true,
component: require('../users/$id.js').default,
},
]
}
users/_layout.js
import styles from './_layout.css';
export default function(props) {
return (
<div className={styles.normal}>
<h1>Page _layout</h1>
<div>
{props.children}
</div>
</div>
);
}
users/index.js
import Link from 'umi/link'
import styles from './index.css';
export default function() {
return (
<div className={styles.normal}>
<h1>用户列表</h1>
<Link to='/users/1'>用户1</Link>
<Link to='/users/2'>用户2</Link>
</div>
);
}
访问localhost:8000/users
点击用户1查看效果
点击用户2查看效果
配置式路由
在根目录下创建config/config.js
配置文件.此配置项存在时则不会对 pages 目录做约定式的解析
export default {
// component是相对于根目录下/pages
routes: [
{ path: '/', component: './index' },
{
path: '/users', component: './users/_layout',
routes: [
{ path: '/users/', component: './users/index' },
{ path: '/users/:id', component: './users/$id' }
]
},
],
};
404路由
约定pages/404.js
为404页面,
路由配置中添加
export default {
// component是相对于根目录下/pages
routes: [
{ path: '/', component: './index' },
{
path: '/users', component: './users/_layout',
routes: [
{ path: '/users/', component: './users/index' },
{ path: '/users/:id', component: './users/$id' }
]
},
{components:'./404.js'}
],
};
权限路由
config/config.js
export default {
// component是相对于根目录下/pages
routes: [
{ path: '/', component: './index' },
//约定为大写Routes
{ path: '/about', component: './about', Routes: ['./routes/PrivateRoute.js'] },
{
path: '/users', component: './users/_layout',
routes: [
{ path: '/users/', component: './users/index' },
{ path: '/users/:id', component: './users/$id' }
]
},
{ path: '/login', component: './login' },
{component:'./404.js'},
],
};
umi g page about #生成about页面
根目录下新建routes/PrivateRoute.js
import Redirect from 'umi/redirect';
export default (props) => {
if(Math.random() > 0.5){
return <Redirect to='/login'/>
}
return (
<div>
{props.children}
</div>
)
}
引入antd
- 添加antd:
npm i antd -S
- 添加umi-plugin-react:
npm i umi-plugin-react -D
- 修改config/config.js
plugins: [
['umi-plugin-react', {
antd: true,
}],
],
page/login.js
import styles from './login.css';
import { Button } from "antd";
export default function() {
return (
<div className={styles.normal}>
<h1>Page login</h1>
<Button type='primary'>按钮</Button>
</div>
);
}
效果展示:
Dvajs
dva是一个基于redux和redux-saga的数据流方案,为了简化开发体验,dva还额外内置了react-router和fetch,所以也可以理解为一个轻量级的应用框架
特点:
1.易学易用
- 仅有 6 个 api,对 redux 用户尤其友好,配合 umi 使用后更是降低为 0 API
2.elm概念
- 通过 reducers, effects 和 subscriptions 组织 model,简化 redux 和 redux-saga 引入的概念
3.插件机制
- 比如 dva-loading 可以自动处理 loading 状态,不用一遍遍地写 showLoading 和 hideLoading
4.支持HMR
- 基于babel-plugin-dva-hmr实现components、routes、和models的HMR
umi中使用dva
page g page goods //创建goods页面
config/config.js修改配置
export default {
// component是相对于根目录下/pages
routes: [
{ path: '/', component: './index' },
{ path: '/goods', component: './goods' }, #添加位置
{ path: '/about', component: './about', Routes: ['./routes/PrivateRoute.js'] },
{
path: '/users', component: './users/_layout',
routes: [
{ path: '/users/', component: './users/index' },
{ path: '/users/:id', component: './users/$id' }
]
},
{ path: '/login', component: './login' },
{ component: './404.js' },
],
plugins: [
['umi-plugin-react', {
antd: true,
dva: true
}],
],
};
配置models
创建models/goods.js
export default {
namesapce: "goods", //model的命名空间,区分多个model
state: [{ title: 'web架构课' }, { title: 'python架构课' }],//初始状态
reducers:{
addGood(state,action){
return [...state,{title:action.payload.title}]
}
}, //更新状态
effects: { //副作用 异步操作
},
}
配置goods.js
import { Component } from 'react';
import styles from './goods.css';
import { connect } from "dva";
import { Card, Button } from "antd";
@connect(
state => ({
goodsList: state.goods //获取指定命名空间的模型状态
}),
{
addGood: title => ({
type: 'goods/addGood', //action的type需要以命名空间为前缀+reducer名称
payload: { title }
}),
}
)
export default class extends Component {
render() {
return (
<div className={styles.normal}>
<h1>Page goods</h1>
<div>
{
this.props.goodsList.map(good => {
return (
<Card key={good.title}>
<div>{good.title}</div>
</Card>
)
})
}
</div>
<div>
<Button onClick={() => this.props.addGood('商品' + new Date().getTime())}>
添加商品
</Button>
</div>
</div>
);
}
}
模拟Mock
创建mock/goods.js
let data = [ //初始状态
{
title: 'web架构课'
},
{
title: 'python架构课'
}
];
export default {
"get /api/goods": function (req, res) {
setTimeout(() => {
res.json({ result: data });
}, 1000);
}
}
models/goods.js
import axios from 'axios'
function getGoods() {
return axios.get('/api/goods')
}
export default {
namesapce: "goods", //model的命名空间,区分多个model
state: [], //初始状态
reducers:{
addGood(state,action){
return [...state,{title:action.payload.title}]
},
initGoods(state,action){
return action.payload
}
}, //更新状态
effects: { //副作用 异步操作
*getList(action, { call, put }) {
const res = yield call(getGoods);
// type的名字 不需要命名空间
yield put({ type: 'initGoods', payload: res.data.result })
}
},
}
goods.js修改
import { Component } from 'react';
import styles from './goods.css';
import { connect } from "dva";
import { Card, Button } from "antd";
@connect(
state => ({
goodsList: state.goods //获取指定命名空间的模型状态
}),
{
addGood: title => ({
type: 'goods/addGood', //action的type需要以命名空间为前缀+reducer名称
payload: { title }
}),
getList: () => ({
type: 'goods/getList',
})
}
)
export default class extends Component {
componentDidMount() {
//调用
this.props.getList()
}
render() {
return (
<div className={styles.normal}>
{/**/}
</div>
);
}
}
**加载状态:**利用内置的dva-loading实现
- 获取加载状态,goods.js
@connect(
state => ({
loading:state.loading
}),
{
...
}
)
export default class extends Component {
render(){
if(this.props.loading.models.goods){
return <div>加载中......</div>
}
....
}
}