Redux for react native 指南(持续更新)

栏目: IOS · Android · 发布时间: 6年前

内容简介:如果要看理论的童鞋点击这里redux中文文档 或者redux官方文档 ,本文不会太刻意去介绍大篇幅的理论,本文不做框架之间的对比,只给想学老规矩先上图:版。

如果要看理论的童鞋点击这里redux中文文档 或者redux官方文档 ,本文不会太刻意去介绍大篇幅的理论,本文不做框架之间的对比,只给想学 redux 的童鞋提供实质的、高效的、易理解的学习参考资源,分享自己在学习过程中的得到(文后有彩蛋 :smile:)。

老规矩先上图:

Redux for react native 指南(持续更新)
看过官方Todos demo的童鞋可能会有点熟悉上图的操作,没错,这就是它的 react native

版。

为什么我要写这个demo

有的童鞋可能会有疑问 问:官方不是 Todos demo吗?为什么还要写这个demo? 答:官方的demo都是 react 的,而并非 react native 的。我也找过很多关于介绍redux的文章,但我发现找到的资料要么太基础、要么介绍不全面、提供的demo下载无法使用等等各种问题,迫使我有了自己动手造轮的冲动,而且这个demo并非只是介绍关于redux的基础的东西,有空我还没陆续更新在使用 redux 过程中的得到,希望大家鼓励支持。

demo采的代码规范

通常一个大项目到后期是需要很多开发者参与的,如果每个开发者都使用自己的一套代码规范做事情,这样带来的后果就是:后期的代码管理工作带来非常大的麻烦,浪费更多的时间去重构,而且也会让新人看代码时理解花更多的时间,还容易把别人带沟里去,所以一个大型项目最初构建架构的时候就必须要遵守一些规范。 那么我们怎么能敲出清爽而又优雅的代码呢?又如何检查我们代码质量合格呢? 我在这里极力推荐遵守 airbnb/javascript 的规范和使用 eslint 来检查自己代码的代码质量(是否遵守了规范),因为它们已经得到了很多公司和开发者的认可。(这里过多的介绍 airbnb eslint ,本文只提供思路,想了解更多自行搜索) 在没有使用代码规范前我们可能用各自的风格写了很多年的代码了,突然要适应这套规范可能非常不适应,没关系,多敲多练习,时间长了就习惯了,谁还没有一个过程,过程是痛苦的,但痛苦过后会给你带来质的升华,自己慢慢领悟体会。 如果还是坚持我行我素,那我也没办法的,我只能说好的东西是会被世界所接受,差的东西最终是要被淘汰的,所以做为一个合格的程序员(特别是前端程序员)要拥抱变化,因为它会使你变得更加的优秀,得到大众的认可。除非你不愿意让自己变得更优秀。(我回看几年前我写的代码,只能用一坨一坨的来形容 ,不知道你们是否也有这种感脚呢?:grin:)

redux能帮我们做什么

两张图示意:

Redux for react native 指南(持续更新)
Redux for react native 指南(持续更新)

redux特性

  • 单一数据源: 整个应用的 state被储存在一棵 object tree 中,并且这个 object tree 只存在于唯一一个 store中。

  • State 是只读的:唯一改变 state 的方法就是触发 action,action 是一个用于描述已发生事件的普通对象。

  • 使用纯函数来执行修改:为了描述 action 如何改变 state tree ,你需要编写 reducers。

  • 预见性:所有的用户的行为都是你提前定义好的。

  • 统一管理state:所有的状态都在一个store中分配管理。

哪些开发者和项目适合用redux

这里只针对 react native 开发而言:

  • 初级:刚接触 react native 我非常不建议去使用,因为你还不知道怎么用它,建议先达到中级。

  • 中级:使用 react native 做出一个以上已经上架的 不复杂 的应用 redux ,也可以不使用,因为使用它并不能让你在前期快速的迭代开发,在这样的项目下使用 redux 就好比 大炮打蚊子 ,副作用很大。但是可以先了解起来,并发现它的优点。这类相对简单的应用:当用户触发一个动作(程序需要 setState({xxx:xxx}) )的时候应用程序状态流程是这样的:

    Redux for react native 指南(持续更新)
  • 高级:使用 react native 做出一个以上已经上架的 复杂 的应用(涉及到即时通讯、界面布局比较复杂,组件嵌套太多层次等),而这类复杂应用:当用户触发一个动作(程序需要 setState({xxx:xxx}) )的时候应用程序状态流程是这样的:

    Redux for react native 指南(持续更新)

这种状态带来的后果,两方面分析:

  • 性能:祖父子组件之间多余的状态传递,导致宝贵的内存资源浪费,同时界面渲染的速度也会变慢,自然用户体验就变差了。
  • 状态管理:当程序不断的迭代,界面布局越来越复杂,必然就会产生许多的 state 状态,那你是如何有效的管理这些状态?是什么原因导致UI多次渲染?是哪一步操作导致的UI组件的变化?在没有使用 redux 前你可能已经发现可以使用生命周期函数中的 shouldComponentUpdate 来减少子组件中没必要的渲染,但终究解决不了状态管理复杂的难题。 当你使用 redux 后,复杂的应用程序状态流程是这样的:
    Redux for react native 指南(持续更新)
    看完上面图文后,是否感觉很好理解我们什么时候要使用 redux 呢?这要感谢 @justjavac 文章提供的动图支持。

redux for react native 工作逻辑图

感谢@黑森林工作室作者提供的清晰的逻辑图

Redux for react native 指南(持续更新)

redux工程结构分析

我对官方的demo小部分位置做了些改造具体看代码分析:

Redux for react native 指南(持续更新)

分工明细

  • js/actions 此文件夹下放内容做的事情是:定义用户行为。
  • js/reducers 此文件夹下放内容做的事情是:响应用户行为,返回改变后的状态,并发送到 store
  • js/components 此文件夹下放内容做的事情是:自定义的组件。
  • js/containers 此文件夹下放内容做的事情是:把 components 文件夹中涉及到状态变化的组件进行第二次封装。
  • App.js 入口文件(store在这里),为什么我要把store定义在这里? 因为它是唯一的,而且必须使用 react-redux 提供的 Provider 组件包裹入口的其他组件才能使 redux 中的 store 生效。
  • global.js 存放全局定义的变量、常量、方法等。

需要注意的事

  • 一个工程中 reduxstore 是唯一的,不能在多个 store
  • 保持 reducer 纯净非常重要。永远不要在 reducer 里做这些操作:
  • 修改传入参数;
  • 执行有副作用的操作,如 API 请求和路由跳转;
  • 调用非纯函数,如 Date.now()Math.random() ;
  • 使用对象展开运算符 ... 代替 Object.assign() 才是最好的解决方案。
  • 组件名首字母要大写,也就是说 componentscontainers 文件夹下的文件首字母都要大写。
  • 应该尽量减少传递到 action 中的数据(能传单个数据就不传对象,能传对象就不传数组)
//good
function todoApp(state = initialState, action) {
  switch (action.type) {
    case SET_VISIBILITY_FILTER:
      return Object.assign({}, state, {
        visibilityFilter: action.filter
      })
    default:
      return state
  }
}
复制代码
//best
function todoApp(state = initialState, action) {
  switch (action.type) {
    case SET_VISIBILITY_FILTER:
      return { ...state, visibilityFilter: action.filter }
    default:
      return state
  }
}
复制代码

#代码详解 js/actions/types.js

//添加列表数据
export const ADD_TODO = 'ADD_TODO';
//筛选
export const SET_VISIBILITY_FILTER = 'SET_VISIBILITY_FILTER';
//文字添加/取消中划线
export const TOGGLE_TODO = 'TOGGLE_TODO';
复制代码

释:

action定义

为什么我要把用户的 action (行为)定义单独抽出来写一个 type.js

  • 方便状态管理。
  • 复用性。

js/actions/index.js

import  {
    ADD_TODO,
    SET_VISIBILITY_FILTER,
    TOGGLE_TODO,
} from './types'
let nextTodoId = 0;

export const addTodo = text => ({
    type: ADD_TODO,
    id: nextTodoId++,
    text
});

export const setVisibilityFilter = (filter) => ({
    type: SET_VISIBILITY_FILTER,
    filter
});

export const toggleTodo = id => ({
    type: TOGGLE_TODO,
    id
});
复制代码

释:

Action 创建函数

Action 创建函数 就是生成 action 的方法。“action” 和 “action 创建函数” 这两个概念很容易混在一起,使用时最好注意区分。

在 Redux 中的 action 创建函数只是简单的返回一个 action:

js/reducers/todos.js

import  {
    ADD_TODO,
    TOGGLE_TODO,
} from '../actions/types'

const todos = (state = [], action) => {
    let {id, text, type} = action;
    switch (type) {
        case ADD_TODO:
            return [
                ...state,
                {
                    id: id,
                    text: text,
                    completed: false
                }
            ];
        case TOGGLE_TODO:
            return state.map(todo => (todo.id === id) ? {...todo, completed: !todo.completed} : todo);
        default:
            return state;
    }
};

export  default  todos;
复制代码

js/reducers/visibilityFilter.js

import { SET_VISIBILITY_FILTER } from '../actions/types'
import { visibilityFilters } from '../global'

const { SHOW_ALL } = visibilityFilters;
const visibilityFilter = (state = SHOW_ALL, action) => {
    let {type, filter} = action;
    switch (type){
        case SET_VISIBILITY_FILTER:
            return filter;
        default:
            return state
    }
};

export default  visibilityFilter;
复制代码

释:

reducer 就是一个纯函数,接收旧的 state 和 action,返回新的 state(上面两个文件可以看着两个reducer)。

注意:

  • Redux 首次执行时, stateundefined ,此时需要设置返回应用的初始 state
  • 每个 reducer 只负责管理全局 state 中它负责的一部分。每个 reducerstate 参数都不同,分别对应它管理的那部分 state 数据。

js/reducers/index.js

import { combineReducers } from 'redux'
import todos from './todos'
import visibilityFilter from './visibilityFilter'

export default combineReducers({
    todos,
    visibilityFilter
})
复制代码

释:

combineReducers() 所做的只是生成一个函数,这个函数来调用你的一系列 reducer,每个 reducer 根据它们的 key 来筛选出 state 中的一部分数据并处理,然后这个生成的函数再将所有 reducer 的结果合并成一个大的对象。

表面上看上去 combineReducers() 的作用就是把多个 reducer 合成一个的 reducer

js/components/Todo.js

import React, { Component } from 'react'
import {
    Text,
    TouchableOpacity
} from 'react-native'
import PropTypes from 'prop-types'

export default class Todo extends Component {
    static propTypes = {
        onClick: PropTypes.func.isRequired,
        completed: PropTypes.bool.isRequired,
        text: PropTypes.string.isRequired
    };

    render(){
        let { onClick, completed, text } = this.props;
        return (
            <TouchableOpacity
                style={{
                    flexDirection: 'row',
                    flex: 1,
                    height: 50,
                    alignItems: 'center',
                    justifyContent: 'center',
                    backgroundColor: '#cccccc',
                    marginTop: 10
                }}
                onPress={onClick}>
                <Text style={{ textDecorationLine: completed ? 'line-through' : 'none'}}>{text}</Text>
            </TouchableOpacity>
        );
    }
}
复制代码

js/components/TodoList.js

import React, { Component } from 'react'
import PropTypes from 'prop-types'
import {
    FlatList
} from 'react-native'
import Todo from './Todo'

export default class TodoList extends Component {
    static propTypes = {
        todos: PropTypes.arrayOf(
            PropTypes.shape({
                id: PropTypes.number.isRequired,
                completed: PropTypes.bool.isRequired,
                text: PropTypes.string.isRequired
            }).isRequired
        ).isRequired,
        toggleTodo: PropTypes.func.isRequired
    };

    _renderItem = (data) => {
       let dataItem = data.item;
       let { id } = dataItem;
       let { toggleTodo } = this.props;
        return (
            <Todo
                {...dataItem}
                onClick={() => toggleTodo(id)}
            />
        )
    };

    render() {
        let { todos } = this.props;
        return (
            <FlatList
                data={todos}
                keyExtractor={(item)=>item.id.toString()}
                renderItem={this._renderItem}
            />
        )
    }
}
复制代码

js/components/Link.js.js

import React, { Component } from 'react'
import PropTypes from 'prop-types'
import {
    TouchableOpacity,
    Text
} from 'react-native'

export default class Link extends Component {
    static propTypes = {
        active: PropTypes.bool.isRequired,
        filter: PropTypes.string.isRequired,
        onClick: PropTypes.func.isRequired
    };

    render() {
        let { active,  filter, onClick } = this.props;
        return (
           <TouchableOpacity
               style={{
                   marginLeft: 4,
                   height: 40,
                   flex:1,
                   borderWidth: 1,
                   borderColor: '#ccc',
                   alignItems: 'center',
                   justifyContent:'center'
               }}
               onPress={onClick}
           >
               <Text style={{fontSize: 10, color: active ? 'black' : '#cccccc'}}>{filter}</Text>
           </TouchableOpacity>
        );
    }
}
复制代码

js/components/Filters.js

import React, { Component } from 'react'
import {
    View,
} from 'react-native'
import FilterLink from '../containers/FilterLink'
import { visibilityFilters } from '../global'

const { SHOW_ALL, SHOW_COMPLETED, SHOW_ACTIVE  } = visibilityFilters;

export default  class Filters extends Component {

    render(){
        return(
            <View style={{ flexDirection: 'row', marginTop: 20}}>
                <FilterLink filter={SHOW_ALL} />
                <FilterLink filter={SHOW_COMPLETED} />
                <FilterLink filter={SHOW_ACTIVE} />
            </View>
        )
    }
}
复制代码
Redux for react native 指南(持续更新)

释:

以上四个文件是自定义的四个展示组件,这些组件只定义外观并不关心数据来源和如何改变。传入什么就渲染什么。如果你把代码从 Redux 迁移到别的架构,这些组件可以不做任何改动直接使用。它们并不依赖于 Redux。

js/containers/AddTodo.js

import React, { Component } from 'react'
import {
    View,
    TextInput,
    Button,
} from 'react-native'
import { connect } from 'react-redux'
import { addTodo } from '../actions'

class AddTodo extends Component {
    constructor(props){
        super(props);
        this.inputValue = '';
    }

    render(){
        let { dispatch } = this.props;
        return (
            <View style={{flexDirection: 'row'}}>
                <TextInput
                    style={{flex:1, borderWidth: 1, borderColor: '#cccccc', textAlign: 'center'}}
                    onChangeText={text => this.inputValue = text}
                />
                <Button title="Add Todo" onPress={() => dispatch(addTodo(this.inputValue))}/>
            </View>
        )
    }
}

export default connect()(AddTodo)
复制代码

js/containers/FilterLink.js

import { connect } from 'react-redux'
import { setVisibilityFilter } from '../actions'
import Link from '../components/Link'

const mapStateToProps = (state, ownProps) => ({
    active: ownProps.filter === state.visibilityFilter,
    filterText: ownProps.filter
});

const mapDispatchToProps = (dispatch, ownProps) => ({
    onClick: () => dispatch(setVisibilityFilter(ownProps.filter))
});

export default connect(
    mapStateToProps,
    mapDispatchToProps,
)(Link)
复制代码

js/containers/VisibleTodoList.js

import { connect } from 'react-redux'
import { toggleTodo } from '../actions'
import TodoList from '../components/TodoList'
import { visibilityFilters } from '../global'

const { SHOW_ALL, SHOW_COMPLETED, SHOW_ACTIVE } = visibilityFilters;

const getVisibleTodos = (todos, filter) => {
    switch (filter) {
        case SHOW_COMPLETED:
            return todos.filter(t => t.completed);
        case SHOW_ACTIVE:
            return todos.filter(t => !t.completed);
        case SHOW_ALL:
            return todos;
        default:
            throw new Error('Unknown filter: ' + filter)
    }
};

const mapStateToProps = state => ({
    todos: getVisibleTodos(state.todos, state.visibilityFilter)
});

const mapDispatchToProps = dispatch => ({
    toggleTodo: id => dispatch(toggleTodo(id))
});

export default connect(
    mapStateToProps,
    mapDispatchToProps,
)(TodoList)
复制代码

释:

以上三个是容器组件,作用是把展示组件连接到 Redux。

有时很难分清到底该使用容器组件还是展示组件。如这个小的组件:

  • AddTodo.js 含有“Add”按钮 和 输入框

技术上讲可以把它分成两个组件,但一开始就这么做有点早。在一些非常小的组件里混用容器和展示是可以的。当业务变复杂后,如何拆分就很明显了。所以现在就使用混合型的吧。

上面出现了使用 react-reduxconnect() 方法来把展示组件和容器组件关联在一起,这个方法做了性能优化来避免很多不必要的重复渲染。(这样你就不必为了性能而手动实现 React 性能优化建议中的  shouldComponentUpdate 方法。)

使用 connect() 前,需要先定义 mapStateToProps 这个函数来指定如何把当前 Redux store state 映射到展示组件的 props 中。例如, VisibleTodoList 需要计算传到 TodoList 中的 todos ,所以定义了根据 state.visibilityFilter 来过滤 state.todos 的方法,并在 mapStateToProps 中使用。

除了读取 state ,容器组件还能分发 action 。类似的方式,可以定义  mapDispatchToProps() 方法接收  dispatch() 方法并返回期望注入到展示组件的 props 中的回调方法。例如,我们希望  VisibleTodoList 向  TodoList 组件中注入一个叫  onTodoClick 的 props ,还希望  onTodoClick 能分发  TOGGLE_TODO 这个 action 。 最后,使用 connect() 创建 VisibleTodoList ,并传入这两个函数。

js/components/Group.js

import React, { Component } from 'react'
import {
    View
} from 'react-native'
import AddTodo from '../containers/AddTodo'
import Filters from '../components/Filters'
import VisibleTodoList from '../containers/VisibleTodoList'

export default class Group extends Component {
    render() {
        return (
            <View style={{paddingHorizontal: 20, paddingVertical: 44}}>
                <AddTodo/>
                <Filters/>
                <VisibleTodoList/>
            </View>
        );
    }
}
复制代码

释:

Group.js 是把所有的关联后的组件串起来,形成一个完整的界面。

App.js

import React, { Component } from 'react'
import { createStore } from 'redux'
import { Provider } from 'react-redux'
import Group from './js/components/Group'
import rootReducer from './js/reducers'

export default class App extends Component {
    render() {
        const store = createStore(rootReducer);
        return (
            <Provider store={store}>
                <Group />
            </Provider>
        );
    }
}
复制代码

释:

入口文件传入 Store

  • 创建 store 传入 reducers
  • 使用 Provider 组件包裹 Group 组件, store 作为属性传入 Provider

进行到这一步,代码分析完毕。本次写作到此结束。我相信大家如果仔细看完的话,多多少少会有些收获吧,如果demo看不太懂,那就跟着代码分析的思路多敲几遍代码,也就理解了,有空我会继续更新未完成的内容。

彩蛋

附上 github demo ReduxForReactNativeDemo 好使的话,别忘了给出宝贵的:heart::star:️,没有比这个更能鼓舞人心的了 :joy:。最后欢迎:clap:指出错误或者发布自己的见解探讨,共勉。

待更新内容

  • Middleware的使用。
  • 配合react-navigation使用。
  • ......

以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

The Ruby Programming Language

The Ruby Programming Language

David Flanagan、Yukihiro Matsumoto / O'Reilly Media, Inc. / 2008 / USD 39.99

Ruby has gained some attention through the popular Ruby on Rails web development framework, but the language alone is worthy of more consideration -- a lot more. This book offers a definition explanat......一起来看看 《The Ruby Programming Language》 这本书的介绍吧!

SHA 加密
SHA 加密

SHA 加密工具

RGB CMYK 转换工具
RGB CMYK 转换工具

RGB CMYK 互转工具