继上次写完 Redux 之后,留下了很多坑,其实这篇也不算是进阶,毕竟只是一些库的使用以及一些小技巧而已,权当是上一篇的填坑了吧。
01 Middleware & Thunk
What's Middlewares?
当我们发送 action 的时候,正常情况下是从 action -> reducer,引入中间件后变为 action -> middlewares -> reducer。为什么要这么做?因为很多时候我们想在 action 传送到 reducer 之前,对数据流进行改变或者进行一些别的什么操作,比如对 action 添加日志,如果一个个去更改 Action Creator 未免太过麻烦,而使用 Middleware 的话就简单很多,可以帮我们省去很多重复代码。最重要的一点,我们可以通过使用 Thunk Middleware 来实现异步 action。
What’s a thunk?
Thunk 就是包装了函数表达式的用于延缓求值的方法。通过下面这个例子可以很好的理解:
// 立即计算求值 1+2
let x = 1 + 2;
// 计算被延迟了,foo 可以在稍后被调用的时候再去计算 1+2,这个时候 foo 方法就是一个 thunk 方法
let foo = () => 1 + 2;
为什么叫 thunk?它是『think』过去式的 幽默式表达(意思是已经想好怎么做了,但是就是还没做 o(´^`)o)。
Redux Thunk
当我们使用了 middleware 的时候,发出的 action 不会直接被 reducer 处理掉,而是先被 middleware 截获,并且我们可以在 middleware 中发起 异步请求。Redux Thunk 就是这样的中间件,允许我们在 Redux 中发起异步请求。
applyMiddleware
在 createStore() 的时候,我们可以指定 enhancer
参数,最常见的就是 applyMiddleware()
,为了在 Redux 中开启对 Redux Thunk 的支持,就需要使用该方法:
import {applyMiddleware, combineReducers, createStore} from "redux";
import thunkMiddleware from 'redux-thunk';
import textReducer from "./reducers/changeText";
const allReducers = combineReducers({textReducer});
let store = createStore(allReducers, applyMiddleware(thunkMiddleware));
export default store;
Return a function
使用了 Redux Thunk 后,我们可以在 Action Creators 中返回一个方法而不是 action,因此我们可以延迟 action 的 dispatch 或者在只有满足条件的时候才 dispatch。内部返回的方法接收的参数分别是 store 的 dispatch
和 getState
方法。举个发起请求获取数据的例子:
export const FETCH_DATA_IN_PROGRESS = 'FETCH_DATA_IN_PROGRESS';
export const FETCH_DATA_SUCCESS = 'FETCH_DATA_SUCCESS';
export const FETCH_DATA_Failure = 'FETCH_DATA_Failure';
function getData(url) {
return async (dispatch, getState) => {
let param = getState().getData.param;
dispatch({type: FETCH_DATA_IN_PROGRESS});
// request 是封装的 fetch 请求
const res = await request.post(url, {param});
if (res && !res.status) {
// 请求成功才发送数据
dispatch({type: FETCH_DATA_SUCCESS, data: res.data});
} else {
dispatch({type: FETCH_DATA_Failure});
}
}
}
上面这个就是一个 thunk function,其使用方式与普通的 Action Creators 一样,可以直接作为第二个参数传入 connect()
方法中:
export default connect(mapStateToProps, {changeText, changeBack, getData})(Main);
当然 Redux Thunk 也不是唯一的发送异步 Action 的方式,比如 redux-saga 以及 redux-promise 都可以达到相同的目的,Redux 文档上还介绍了一些别的方式:
Thunk middleware isn't the only way to orchestrate asynchronous actions in Redux:
- You can use redux-promise or redux-promise-middleware to dispatch Promises instead of functions.
- You can use redux-observable to dispatch Observables.
- You can use the redux-saga middleware to build more complex asynchronous actions.
- You can use the redux-pack middleware to dispatch promise-based asynchronous actions.
- You can even write a custom middleware to describe calls to your API, like the real world example does.
It is up to you to try a few options, choose a convention you like, and follow it, whether with, or without the middleware.
02 Higher-Order Components
What's HOC?
HOC 即 Higher-Order Components 高阶组件的简称,从形式上来看,其实就是接收函数作为参数的函数。
HOC 是 React 中的一种模式,通过 HOC 我们可以方便地在多个组件中注入一些通用的功能,这样就可以避免重复的代码逻辑。一个 HOC 函数接收一个组件作为参数,并且返回一个新的组件,通过 HOC 函数我们可以为组件添加额外的功能或者数据。
在 RN 中,一种常见的使用方式是通过 HOC 函数作为页面跳转的依据。比如检验有无登录,如果页面需要登录后才能查看,那么用户在未登录的情况下会先跳转登录页。
How to use it?
HOC 的使用方法很简单,形式如下:
const hoc = (WrappedComponent) => {
class HOC extends React.Component {
render() {
return <WrappedComponent />;
}
}
return HOC;
};
这里的匿名函数接收的参数是 WrappedComponent
,即我们需要包装的组件,返回的是我们对包装的组件进行处理之后的新组件。其使用方式如下:
const myHOC = hoc(MyComponent);
除此之外,我们也可以结合 React Redux 使用:
const hoc = (WrappedComponent) => connect(mapStateToProps, mapDispatchToProps)(
class HOC extends React.Component {
render() {
return <WrappedComponent />;
}
}
);
其实这里的 connect()
方法就是一个 HOC 的例子,通过连接组件和保存在 Store 中的全局 state,同时在组件中可以通过 props 的形式来访问这些全局 state。
03 Combine with react-native-router-flux
介绍了 Thunk Middleware 和 HOC,接下来我想用一个例子展示如何在项目中使用他们。这个例子使用到了 React Native Router,这是一个非常好用的页面路由、页面导航以及页面间传递数据的 RN 框架。这里我只用到了其中很小一部分的功能,更多的用法请移步 API 文档。
照惯例,先看下实现的效果:Demo GIF
可以看到,我们在原来的基础上添加了两个新的页面,一个是登录后的页面,一个是登录页,连接它们的是一个 HOC 函数:
const verifyLogin = WrappedComponent => connect(mapStateToProps)(
class extends Component {
render() {
if (this.props.authToken) {
return (<WrappedComponent {...this.props} />);
} else {
return (<Login/>);
}
}
}
);
function mapStateToProps(state) {
return {
authToken: state.authInfo.data,
};
}
export default verifyLogin;
在 HOC 中,我们根据 authToken
的状态来决定是直接跳转还是先跳转到登录页。这个例子很好的说明了 HOC 函数的优势,所有需要登录的地方都只要调用这个函数就可以了。这里的 authToken
是从 store 中获取的,因此我们还得写一个模拟登录的 Action Creator 来进行登录获取 token
,同时展示下如何使用 Redux Thunk 发起异步请求:
export function login(info) {
return async (dispatch, getState) => {
dispatch({type: Request_login_requesting});
// 模拟发起请求并获取结果
let res = 'fakeAuthRequestAndGetResult';
// 根据请求结果发送不同的 action
if (res) {
dispatch({type: Request_login_success, data: res});
} else {
dispatch({type: Request_login_failure});
}
}
}
另外,还有对应的 reducer
,注意,combineReducers()
接收的参数为对象:
function data(state = '', actions) {
switch(actions.type){
case Request_login_success:
case Request_login_failure:
return actions.data;
case Request_logout:
return '';
default:
return state;
}
}
let authInfo = combineReducers({data});
export default authInfo;
最后,除了两个新页面之外,我们还需要定义一个 router 页,也就是使用 React Native Router 来管理各个页面:
class AppRouter extends Component {
render() {
return <Router>
<Stack>
<Scene key='root' component={Main} />
<Scene key='personal' component={Personal} />
<Scene key='login' component={Login} />
</Stack>
</Router>
}
}
export default AppRouter;
然后在 App 的入口处使用 AppRouter
替换原来的 Main
:
export default class Root extends Component {
render() {
return (
// 第一层包装,连接组件和 store
<Provider store={store}>
<AppRouter/>
</Provider>
)
}
}
OK,核心代码就是这样了,完整代码:aJIEw/Redux
04 Sum Up
这篇写的比较杂,一开始只是想写下 Redux 中的 middleware,后来看了这篇 Redux-Thunk vs. Redux-Saga,发现对于大多数场景下,的确使用 Redux-Thunk 就足够了。而 HOC 也是临时想到要写一写的,毕竟也算是 React 中一种常见的模式了吧。最后这个结合 react-native-router 写的例子也比较简单,主要用来说明 Redux-Thunk 发异步请求以及 HOC 的大致用法。
好了,写完这篇有种『我已经掌握 React Native 开发了』的错觉,但其是内心还是很慌的,因为知道要掌握的东西还有太多。不过一口吃不成个胖子,只能静下心来一步一脚印慢慢往前走了。每天进步一点点,坚持下去,收获就是巨大的。嗯,加油~(•̀ᴗ•́)و ̑̑
网友评论