React Native - 从 Redux 进阶谈起

Autumn Wind

继上次写完 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
3
4
5
// 立即计算求值 1+2
let x = 1 + 2;

// 计算被延迟了,foo 可以在稍后被调用的时候再去计算 1+2,这个时候 foo 方法就是一个 thunk 方法
let foo = () => 1 + 2;

为什么叫 thunk?它是『think』过去式的 幽默式表达(意思是已经想好怎么做了,但是就是还没做⸜(ّᶿധّᶿ)⸝)。

Redux Thunk

当我们使用了 middleware 的时候,发出的 action 不会直接被 reducer 处理掉,而是先被 middleware 截获,并且我们可以在 middleware 中发起 异步请求。Redux Thunk 就是这样的中间件,允许我们在 Redux 中发起异步请求。

applyMiddleware

createStore() 的时候,我们可以指定 enhancer 参数,最常见的就是 applyMiddleware(),为了在 Redux 中开启对 Redux Thunk 的支持,就需要使用该方法:

1
2
3
4
5
6
7
8
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 的 dispatchgetState 方法。举个发起请求获取数据的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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() 方法中:

1
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:

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 的使用方法很简单,形式如下:

1
2
3
4
5
6
7
8
const hoc = (WrappedComponent) => {
class HOC extends React.Component {
render() {
return <WrappedComponent />;
}
}
return HOC;
};

这里的匿名函数接收的参数是 WrappedComponent,即我们需要包装的组件,返回的是我们对包装的组件进行处理之后的新组件。其使用方式如下:

1
const myHOC = hoc(MyComponent);

除此之外,我们也可以结合 React Redux 使用:

1
2
3
4
5
6
7
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 文档

照惯例,先看下实现的效果:

可以看到,我们在原来的基础上添加了两个新的页面,一个是登录后的页面,一个是登录页,连接它们的是一个 HOC 函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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 发起异步请求:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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() 接收的参数为对象:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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 来管理各个页面:

1
2
3
4
5
6
7
8
9
10
11
12
13
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:

1
2
3
4
5
6
7
8
9
10
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 开发了』的错觉,但其是内心还是很慌的,因为知道要掌握的东西还有太多。不过一口吃不成个胖子,只能静下心来一步一脚印慢慢往前走了。每天进步一点点,坚持下去,收获就是巨大的。嗯,加油~(•̀ᴗ•́)و ̑̑


相关文章:React Native - Redux 入门

参考文章:

  1. React 项目中 Redux 中间件的理解
  2. Redux 入门教程(二):中间件与异步操作
  3. Redux-Thunk 快速入门
  4. Understanding how redux-thunk works
  5. 面向初学者的高阶组件教程
  6. Understanding React Higher-Order Components by Example