mindjet

mindjet 查看完整档案

深圳编辑  |  填写毕业院校xx  |  xx 编辑 mindjet.github.io 编辑
编辑

// Android Lover & Frontend Newbie ❤ //

Github: https://github.com/Mindjet
博客:http://mindjet.github.io
掘金:https://juejin.im/user/5864be...
Email:pearl920@outlook.com

个人动态

mindjet 赞了文章 · 2018-01-04

React 项目中Redux 中间件的理解

前言

React/Redux项目结束后,当我在研究react-router源码的时候发现当中有一部分含中间件的思想,所以才想把中间件重新梳理一遍;在之前看redux了解到中间件,redux层面中间件的理解对项目前期比较有帮助,虽然项目中后期基本可以忽略这层概念;现在对这部分的笔记重新梳理,这里只针对这个中间件做一个理解。

如果想学习项目的底层建设,建议先去学习官网redux案例,之后在学习react-router的使用

Redux 中间件介绍

Redux 目的是提供第三方插件的模式,改变action -> reducer 的过程。变为 action -> middlewares -> reducer 。自己在项目中使用它改变数据流,实现异步 action ;下面会对日志输出做一个开场。

使用 Redux 中间件

Redux 中 applyMiddleware 的方法,可以应用多个中间件,这里先只写一个中间件,以日志输出中间件为例

//利用中间件做打印log
import {createStore,applyMiddleware} from 'redux';
import logger from '../api/logger';
import rootReducer from '../reducer/rootReducer';


let createStoreWithMiddleware = applyMiddleware(logger)(createStore);
let store = createStoreWithMiddleware(rootReducer);
// 也可以直接这样,可以参考createStore
// createStore(
//     rootReducer,
//     applyMiddleware(logger)
// )
export default store;

logger 中间件结构分析

const logger = store => next => action => {
    let result = next(action); // 返回的也是同样的action值
    console.log('dispatch', action);
    console.log('nextState', store.getState());
    return result;
};

export default logger;

store => next => action =>{} 实现了三层函数嵌套,最后返回 next ,给下一个中间件使用,接下来把三层函数拆解;

从applyMiddleware源码开始分析

///redux/src/applyMiddleware.js
export default function applyMiddleware(...middlewares) {
    return (createStore) => (reducer, initialState, enhancer) => {
        var store = createStore(reducer, initialState, enhancer)
        var dispatch = store.dispatch
        var chain = []
        var middlewareAPI = {
            getState: store.getState,
            dispatch: (action) => dispatch(action)
        }
        chain = middlewares.map(middleware => middleware(middlewareAPI))
        dispatch = compose(...chain)(store.dispatch)
        return {
            ...store,
            dispatch
        }
    }
}
最外层store
//源码分析
chain = middlewares.map(middleware => middleware(middlewareAPI));

我们发现store是middlewareAPI,

//store
var middlewareAPI = {
    getState: store.getState,
    dispatch: (action) => dispatch(action)
}

然后就剩下

next => action => {
    let result = next(action); // 返回的也是同样的action值
    console.log('dispatch', action);
    console.log('nextState', store.getState());
    return result;
};
中间层next
//源码分析
dispatch = compose(...chain)(store.dispatch)

先来分析compose(...chain)

//compose源码
export default function compose(...funcs) {
    if (funcs.length === 0) {
        return arg => arg
    }

    if (funcs.length === 1) {
        return funcs[0]
    }

    const last = funcs[funcs.length - 1]
    const rest = funcs.slice(0, -1)
    return (...args) => rest.reduceRight((composed, f) => f(composed), last(...args))
}

compose利用Array.prototype.reduceRight的方法

//reduceRight遍历介绍
[0, 1, 2, 3, 4].reduceRight(function(previousValue, currentValue, index, array) {
    return previousValue + currentValue;
}, 10);

//结果 10+4+3+2+1+0 = 20

因为我们这里的中间件就只有一个,所以没有使用到reduceRight直接返回,直接返回func[0](本身);再由compose(...chain)(store.dispatch),我们可以知道next就是store.dispatch

(action) => {
    let result = store.dispatch(action); // 这里的next就是store.dispatch
    console.log('dispatch', action);
    console.log('nextState', store.getState());
    return result;
};

我们之后调用的dispath就是触发的是上面这个函数(这里就单个中间件);

多个中间件

  • 通过上面的 applyMiddleware , compose 和中间件的结构,

  • 假设应用了如下的中间件: [A, B, C],这里我们使用es5的结构做分析

  • 分析action触发的完整流程

三个中间件

//A
function A(store) {
    return function A(next) {
        return function A(action) {
            /*...*/;
            next(action);
            /*...*/;
            return /*...*/;
        }
    }
}
//B
function B(store) {
    return function B(next) {
        return function B(action) {
            /*...*/;
            next(action);
            /*...*/;
            return /*...*/;
        }
    }
}
//C
function C(store) {
    return function C(next) {
        return function C(action) {
            /*...*/;
            next(action);
            /*...*/;
            return /*...*/;
        }
    }
}

通过chain = middlewares.map(middleware => middleware(middlewareAPI)),三个中间件的状态变化

//A
function A(next) {
    return function A(action) {
        /*...*/;
        next(action);
        /*...*/;
        return /*...*/;
    }
}
//B
function B(next) {
    return function B(action) {
        /*...*/;
        next(action);
        /*...*/;
        return /*...*/;
    }
}
//C
function C(next) {
    return function C(action) {
        /*...*/;
        next(action);
        /*...*/;
        return /*...*/;
    }
}

再由dispatch = compose(...chain)(store.dispatch),我们转化下

const last = C;
const rest = [A,B]
dispatch = rest.reduceRight(
    (composed, f) =>{
        return f(composed)
    }, 
    last(store.dispatch)
)

我们得到的结果

dispatch = A(B(C(store.dispatch)));

进一步分析,我们得到的结果

dispatch = A(B(C(store.dispatch)));

//执行C(next),得到结果

A(B(function C(action) {/*...*/;next(action);/*...*/;return /*...*/;})); 
//此时的next = store.dispatch

//继续执行B(next)
A(function B(action) {/*...*/;next(action);/*...*/;return /*...*/;});    
//此时的next = function C(action) {/*...*/;next(action);/*...*/;return /*...*/;}

//继续执行A(next)
function A(action) {/*...*/;next(action);/*...*/;return /*...*/;};
//此时的next = function B(action) {/*...*/;next(action);/*...*/;return /*...*/;}

一个action触发执行顺序,A(action) -> B(action) -> C(action) -> store.dispatch(action)(生产最新的 store 数据);

如果next(action)下面还有需要执行的代码,继续执行 C(next 后的代码)->B(next 后的代码)->A(next 后的代码)

总结:先从内到外生成新的func,然后由外向内执行。本来我们可以直接使用store.dispatch(action),但是我们可以通过中间件对action做一些处理或转换,比如异步操作,异步回调后再执行next;这样的设计很巧妙,只有等待next,才可以继续做操作,和平时直接异步回调又有些不一样

项目实践 ->异步

我们知道redux中actions分为actionType,actionCreator,然后在由reducer进行修改数据;

官方例子中async直接在actionCreator做了ajax请求;

我们把ajax放入中间件触发下面要讲的与官方real-world类似

我这边使用redux-thunk

applyMiddleware(reduxThunk, api)

先来看看redux-thunk的源码

function createThunkMiddleware(extraArgument) {
    return ({ dispatch, getState }) => next => action => {
        if (typeof action === 'function') {//重新分发
            return action(dispatch, getState, extraArgument);
        }
        return next(action);//传递给下一个中间件
    };
}

const thunk = createThunkMiddleware();
thunk.withExtraArgument = createThunkMiddleware;

export default thunk;

这样一来我们可以把异步写成一个复用的actionCreator;

import * as types from '../../constants/actions/common';

export function request(apiName, params, opts = {}) {
    return (dispatch, getState) => {
        let action = {
            'API': {
                apiName: apiName,
                params: params,
                opts: opts
            },
            type: types.API_REQUEST
        };
        return dispatch(action);
    };
}


//其他地方调用复用的方法如下:
export { request } from './request';

正常的写法,不是异步的,就是之前的写法

export function cartSelect(id) {
    return { 
        type: types.CART_MAIN_SELECT, 
        id
    };
}

然后就是下一个中间件的处理 api.js

//自己封装的ajax,可以使用别的,比如isomorphic-fetch
import net from 'net';
//项目中全部的接口,相当于一个关于异步的actionType有一个对应的后端接口
import API_ROOT from 'apiRoot';

export default store => next => action => {
    let API_OPT = action['API'];

    if (!API_OPT) {
        //我们约定这个没声明,就不是我们设计的异步action,执行下一个中间件
        return next(action);
    }

    let ACTION_TYPE = action['type'];
    let { apiName, params = {} , opts = {} } = API_OPT;
    /**
     * 如果有传递localData,就不会触发ajax了,直接触发_success
     * 当前也可以传其他参数
     */
    let { localData } = opts;
    let {
        onSuccess,
        onError,
        onProgress,
        ajaxType = 'GET',
        param
    } = params;
    // 触发下一个action
    let nextAction = function(type, param, opts) {
        action['type'] = type;
        action['opts'] = opts;
        delete param['onSuccess'];
        delete param['onError'];
        const nextRequestAction = {...action,...param}
        return nextRequestAction;
    };

    params={
        ...params,
        data: null
    };
    // 触发正在请求的action
    let result = next(nextAction(apiName + '_ON', params, opts));
    net.ajax({
        url: API_ROOT[apiName],
        type: ajaxType,
        param,
        localData,
        success: data => {
            onSuccess && onSuccess(data);
            params={
                ...params,
                data
            };
            //触发请求成功的action
            return next(nextAction(apiName + '_SUCCESS', params, opts));
        },
        error: data => {
            onError && onError(data);
            //触发请求失败的action
            return next(nextAction(apiName + '_ERROR', params, opts));
        }
    });

    return result;
};

强调一点:项目中全部的接口,相当于一个关于异步的actionType有一个对应的后端接口,所以我们才可以通过API_ROOT[apiName]找到这个接口

以cart为列子(下面是对应的每个文件):

actionType:

//异步
export const CART_MAIN_GET = 'CART_MAIN_GET';
//非异步
export const CART_MAIN_SELECT = 'CART_MAIN_SELECT';

api:

const api = {
    'CART_MAIN_GET':'/shopping-cart/show-shopping-cart'
};
export default api;

APIROOT修改:

import cart from './api/cart';
const APIROOT = {
    ...cart
};
export default API;

actionCreator:

//项目中使用redux的bindActionCreators做一个统一的绑定,所以在这里单独引入
export { request } from './request';
//下面是非异步的方法
export function cartSelect(id) {
    return { 
        type: types.CART_MAIN_SELECT, 
        id
    };
}

项目中发起结构是这样的:

let url = types.CART_MAIN_GET;
let param = {};
let params = {
    param: param,
    ajaxType: 'GET',
    onSuccess: (res) => {
        /*...*/
    },
    onError: (res) => {
        /*...*/
    }
};
request(url, params, {});

其对应的reducers就是下面

import * as types from '../constants/actions/cart';
const initialState = {
    main:{
        isFetching: 0,//是否已经获取 
        didInvalidate:1,//是否失效
        itemArr:[],//自定义模版
        itemObj:{},//自定义模版数据
        header:{}//头部导航
    }
};
export default function(state = initialState, action) {
    let newState;
    switch (action.type) {
        case types.HOME_MAIN_GET + '_ON'://可以不写
            /*...*/
            return newState;
        case types.HOME_MAIN_GET + '_SUCCESS':
            /*...*/
            return newState;
        case types.HOME_MAIN_GET + '_ERROR'://可以不写
            /*...*/
            return newState;
        default:
            return state;
    }
};

异步,数据验证都可以通过中间件做处理;引用Generator,Async/Await,Promise处理,可以参考社区中的一些其他方式,比如:

查看原文

赞 8 收藏 24 评论 1

mindjet 回答了问题 · 2017-12-29

Android View属性动画实现放大效果出现的问题!!

这个跟变换的中心点有关,但是 ObjectAnimator 貌似没有可以设置变换中心点的 api,当然你可以可以再叠加一个位移的 ObjectAnimator 来修正你的错误。

这里我使用了普通的 Animation 来实现你说的功能。

  • res/anim 下定义动画文件 simple.anim.xml
<?xml version="1.0" encoding="utf-8"?>
<scale xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="1000"
    android:fromXScale="1"
    android:fromYScale="1"
    android:pivotX="0"
    android:pivotY="1"
    android:toXScale="1.6"
    android:toYScale="1.6"/>

其中,pivotX/pivotY 属性是定义变换的中心点,这里定在左下角

  • 使用动画
Animation anim = AnimationUtils.loadAnimation(this, R.anim.simple_anim);
anim.setFillAfter(true);
view.startAnimation(anim);

注意要使用 setFillAfter 来让变换后的大小保持。

但是这种做法有一个缺点:变大后的 view 其有效的点击面积还是跟变换前一样,也就是说,有一些区域是不可点击的。

关注 3 回答 2

mindjet 回答了问题 · 2017-12-20

解决andriod 应用更新问题

平台上版本的查询各平台基本都会提供 SDK,但是一般应用会在多个平台上发布,只检测一个平台上的版本不够合理。最好是跟楼上说的一样,在服务器维护版本号和 apk,通过发送请求来检测是否有新版本,并且从服务器上下载。

关注 5 回答 3

mindjet 回答了问题 · 2017-12-12

为啥我写了点击事件在点击 确定,取消时没有显示相应的结果

有点疑惑,为什么你代码里面有两处为 btnConfirmbtnCancel 绑定点击事件?

关注 2 回答 1

mindjet 回答了问题 · 2017-12-12

解决android的FrameLayout布局的常规用途?

FrameLayout,帧布局,布局下的元素有层叠效果。

至于你说的报错,建议把错误栈贴上来看看。

关注 5 回答 4

mindjet 回答了问题 · 2017-12-06

Android-studio运行结果都是Process finished with exit code 0

因为你 run 的任务选错了,修改运行按钮左边的选项。

关注 2 回答 1

mindjet 回答了问题 · 2017-11-30

安卓读取/system/etc/mac.txt文件的内容一定需要root吗?

应该不需要 root,可以试试以下命令:

adb pull /system/etc/mac.txt

mac.txt 抓取到电脑。

关注 3 回答 2

mindjet 赞了文章 · 2017-11-28

flask 爬坑指南(一)如何开始一个flask应用

前言

去年十月开始学习python一开始写了一个python的爬虫
将自己在过程中的一些经验写了下来没想到那么多人支
持。之后因为一些实验室的需求就转投python的web开发
一开始用的Django但是感觉她的集成度太高。然后在过
年的时候学了Flask这学期就用Flask开发了实验室元器件
管理系统。但目前也只是处于能用状态。但还是想把自己
在开发过程中遇到的问题和解决方案发出来。希望这一
系列教程可以给还在摸索中的同学提供一些帮助

项目的 Github 连接

教程目录

一:如何开始一个flask项目

0x01:一个好的开始,目录结构

--app  
  --static
  --templates
  --__init__.py
  --models.py
  --views.py
--run.py
--manage.py
--readme.md
--c

嗯就是这样
其中app文件夹里面的就是你最后发布在服务器上的东西
app里面的static文件夹存放css文件,templates文件夹存放
html文件
__init__.py文件是flask启动时候的一些初始化工作

from flask import Flask
app=Flask(__name__)
app.config.from_object('config')

from app import views,modes

models .py 文件是存放你的模型对象的 说人话就是
你的一些数据结构声明,比如你的网页有个个人介绍
其中这个人有很多信息然后你把这个人抽象为一个对象
然后把这个对象的声明放在models.py文件中。
暂时在第一部分中这个这个文件还没有代码

views .py 文件是存放你的视图函数的 说人话就是这个
文件是用来解析的URl的及对你不同的url在后台进行不
同的处理后返回给前端页面。这个文件我们需要写一些
代码。

from app import app
@app.route('/')
def index():
    return 'Hello World'

在开始我对from app import app 这句话感到很费解
这两个app都是哪的呢?后来发现是自己的python
没有学好这个就涉及到python的模块的问题。
及如何将一个文件夹下文件变成一个包呢。重要
的就是要在这个文件夹下面有一个__init__.py的
文件。但有了这个文件后你的这个文件夹就成了一个
python包(好像是这样的,我看了一些资料是这样
说的。要是不对请大家在评论给我指出)
那么from app 就可以解释的通,及这个包名(文件夹
名字)是app。然后我们还在__init__.py中声明了一个
app对象,这样两个app就可以解释清楚了。及从app
包中引入了app对象

@app.route('/')这个就是对url的解析
也就是当你在前端访问一个网页在后端就会调用这个
修饰器下的函数

run . py这个就是启动整个应用的启动文件了

from app import app
if __name__ =="__main__":
    app.run(debug=True)

这样就启动了整个应用 我们在app.run里面传入了一个
参数,这个参数在你调试的时候有很多好处。首先
当你修改你的文件并保存后,你运行的应用就会自动重启
加载新修改的程序。并且在你运行的函数有错误时会返回
错误参数。而不仅仅是一个400服务器无法理解此请求。

效果图

图片描述

访问127.0.0.1:5000端口就可以打开页面
这样一个初始的flask应用就搭建完成了

第一部分教程就是很简单的一个flask应用
下一个就是无数坑的部署。一说起来满眼
都是泪啊。敬请期待,马上就推出

查看原文

赞 3 收藏 8 评论 0

mindjet 赞了回答 · 2017-11-28

解决安卓权限问题

明显有这样的 API: PermissionInfo
以 kotlin 为例使用如下代码.

        val permissionInfo =  packageManager.getPermissionInfo("android.permission.ACCESS_FINE_LOCATION", PackageManager.GET_META_DATA)
        val desc = permissionInfo.loadDescription(packageManager)
        val label = getString(permissionInfo.labelRes)
        println("name:${permissionInfo.name}")
        println("desc:${desc}")
        println("label:${label}")

输出如下:(因为我是在模拟器下跑的,默认是英文)

11-25 03:42:07.604 I/System.out: name:android.permission.ACCESS_FINE_LOCATION
11-25 03:42:07.604 I/System.out: desc:This app can get your location based on GPS or network location sources such as cell towers and Wi-Fi networks. These location services must be turned on and available on your phone for the app to be able to use them. This may increase battery consumption.
11-25 03:42:07.605 I/System.out: label:access precise location (GPS and network-based)

关注 3 回答 2

mindjet 回答了问题 · 2017-11-24

解决安卓权限问题

应该没有类似的 API,因为本身定义一个权限本身就没有太多内容,更没有详情。

关注 3 回答 2

认证与成就

  • 获得 14 次点赞
  • 获得 3 枚徽章 获得 0 枚金徽章, 获得 0 枚银徽章, 获得 3 枚铜徽章

擅长技能
编辑

开源项目 & 著作
编辑

  • LiteReader

    一款基于 MD 的极轻阅读 App,提供知乎日报、豆瓣电影等资源

  • LiteWeather

    一款用 Kotlin 编写,基于 MD 风格的轻量天气 App

注册于 2016-03-27
个人主页被 690 人浏览