React Native - 从 Redux 进阶谈起

Reeds in the 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
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 的 dispatchgetState 方法。举个发起请求获取数据的例子:

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:

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


相关文章: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
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,723评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,003评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,512评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,825评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,874评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,841评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,812评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,582评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,033评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,309评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,450评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,158评论 5 341
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,789评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,409评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,609评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,440评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,357评论 2 352

推荐阅读更多精彩内容