v-api-wizarddeprecated

a helper utility to simplify the usage of REST api with vuex.

Usage no npm install needed!

<script type="module">
  import vApiWizard from 'https://cdn.skypack.dev/v-api-wizard';
</script>

README

v-api

一个帮助简化使用Vuex发起HTTP(REST API)请求的工具。支持Vuex 2,默认使用流行的HTTP客户端axios发起请求。

目录

解决什么问题?

如果你使用Vuex来发起REST API请求,你会发现需要几个重复的步骤。你需要通过一个api请求来获得数据(通过action),然后设置state(通过mutation)。这个工具可以帮助你生成一个store,只需按照简单的形式设置,它就会自动帮你生成state、mutations、actions,然后自动处理请求并改变state。

它并不是一个Vuex plugin,仅仅只是一个帮助简化生成Store对象的工具,你可以对你任何不满意的地方进行重写。

简单的示例

import Vapi from 'v-api-wizard';

const posts = Vapi({
    baseURL: '/v1/client',
    state: { posts: [] }
})
    .get({
        action: 'getPost', // action name
        property: 'post', // state property name
        path: ({ id }) => `/posts/${id}`
    })
    .get({
        action: 'listPosts',
        property: 'posts',
        path: '/posts'
    })
    .post({
        action: 'updatePost',
        property: 'post',
        path: ({ id }) => `/posts/${id}`
    })
    .getStore(); // create store object

Vuex.Store(posts);

// 然后正常发起action
this.listPosts() // with mapActions

安装

yarn add v-api-wizard

用法

使用步骤

  1. Import v-api-wizard(我叫做Vapi)。

  2. 创建一个Vapi实例。

    创建实例时,可以传入实例选项。建议你应该始终传入一个baseUrl来表示向哪里发起请求。另外,可选的传入axios实例,也可以初始化默认的state。如果没有设置默认的statev-api会自动根据property设置为null

  3. 添加actions。

    每个action都表示一个vuex action。当它被调用时(action属性),会发起一个特定的请求(path属性),然后会根据请求的返回值去设置对应的property

  4. 生成store对象。

    手动调用Vapi实例的getStore方法,或者将Vapi实例传入createStore方法。

  5. 将它传入Vuex。

// step 1
import Vapi from 'v-api';

// step 2
const posts = Vapi({
    baseURL: '/v1/client',
    state: { posts: [] }
})
    // step 3
    .get({
        action: 'getPost', // action name
        property: 'post', // state property name
        path: ({ id }) => `/posts/${id}`
    })
    .get({
        action: 'listPosts',
        property: 'posts',
        path: '/posts'
    })
    .post({
        action: 'updatePost',
        property: 'post',
        path: ({ id }) => `/posts/${id}`
    })
    // step 4
    .getStore(); // create store object

// step 5
Vuex.Store(posts);

生成的store如下:

// will got this
{
   namespaced: false,
   state: {
       pending: {
           posts: false,
           post: false
       },
       error: {
           posts: null,
           post: null
       },
       posts: [],
       post: null
   },
   mutations: {
       LIST_POSTS: Function,
       LIST_POSTS_SUCCEEDED: Function,
       LIST_POSTS_FAILED: Function,
       GET_POST: Function,
       GET_POST_SUCCEEDED: Function,
       GET_POST_FAILED: Function,
       UPDATE_POST: Function,
       UPDATE_POST_SUCCEEDED: Function,
       UPDATE_POST_FAILED: Function
   },

   actions: {
       listPosts: Function,
       getPost: Function,
       updatePost: Function
   }
}

重要概念

v-api中需要明白三个重要的概念Vapi实例、actionstore

  • Vapi实例:是通过Vapi构造函数与选项参数生成的对象,它主要包含了一些请求处理参数,默认的state,以及action的部分预定义参数。

  • action:通过Vapi实例的add(或者其他快捷方法,如:get)方法添加的action。此处的action不同于Vuex中的action,这里仅仅只是包含action的一些预定义,并没有真正生成action函数。

    同样可以传入action选项,每调用一次add方法,都会产生一个新的action。执行action时,可以传入提交参数。

  • store:这里的是store与Vuex store(module)的概念是一致的。通过调用Vapi.getStore()产生真正的actionmutation等。

链式调用

如示例,Vapi可进行链式调用

http快捷方法

add方法用于添加一个action。对于任何合法的HTTP methods,还提供了快捷方法。

add(options: Object): Vapi
get(options: Object): Vapi
delete(options: Object): Vapi
head(options: Object): Vapi
options(options: Object): Vapi
post(options: Object): Vapi
put(options: Object): Vapi
patch(options: Object): Vapi

提交action

post等请求使用data字段指定需要提交的数据,get请求使用params字段指定查询参数(与axios保持一致)。

this.listPosts({
    params: {
        ...
    }
});
this.updatePost({
    data : {
        ...
    }
})

提交action时,除了可以传递请求参数以外,还可以传递其他提交参数,比如:meta、resolved、rejected、after、before等。

{
    params,
    data,
    meta,
    after,
    before,
    resolved,
    rejected,
    ...other
}

除预留的提交参数之外,甚至可以提交其他任意的数据。这些数据将与请求结果一起被传递到resolved/rejected回调、successHandler/errorHandler等地方。但是这些数据并不会被v-api自动处理。

详情请参考Api说明。

自定义请求处理方法

可以为action指定successHandlererrorHandler覆盖默认的自动改变state的行为。如果你想自己决定怎么改变state,那它很有用。

add({
    action: 'listPosts',
    property: 'posts',
    path: '/posts',
    method: 'get',
    // successHandler or errorHandler的函数签名实际上与Vuex的mutation一样
    // 第一个参数仍然是store的state,第二个参数为commit载荷。这里为请求的返回结果。
    successHandler(state, payload) {
        state.posts = payload.data.list;
    }
})

此时,Vapi将不会自动改变state,而是使用指定的successHandler

生成普通action

Vapi不仅能添加HTTP请求cation,也能发起一个普通的action

add({
    action: 'changePageNum',
    property: 'page'
})

如果不指定path参数,则成为一个普通action(不需要发起请求),此时会自动将changePageNum的载荷赋值给page

this.changePageNum({
    num: 1
}); // state.page = { num : 1}

此时,也可以指定successHandler来改变默认的赋值行为。注意:此处不能添加errorHandler,因为这里没有请求行为。之所以仍然使用successHandler,是以为我不想给你增加多记住一个参数的负担。

headerpath中使用参数

Vapi允许将headerspath指定为函数。

get({
    action: 'getPost', // action name
    property: 'post', // state property name
    path: ({ id }) => `/posts/${id}`
})

Vapi使用meta参数为headerspath提供元数据。meta可以在提交action时指定,也可以在action定义时指定,还可以在Vapi实例上指定。

const posts = Vapi({
    baseURL: '/v1/client',
    state: { posts: [] },
    meta: { userName: 'test' }
})
    .get({
        action: 'getPost', // action name
        property: 'post', // state property name
        meta: { queryAll: 1 },
        path: ({ id, queryAll }) => `/posts/${id}?queryAll=${queryAll}` // { userName: 'test', queryAll: 1, id: 123 }
    })

// width mapActions
this.getPost({ meta: { id : 123 } });

meta会自动合并Vapi实例、action定义及action提交上的meta字段。

headerspath相同。

使用回调

Vapi允许使用resovledrejected参数指定请求成功与失败时的回调。回调可以发起另一个action,也可以执行一个函数。

参数类型:[String | Object | Function | Array: [String, Function, Object, Array]]

  • String类型: 回调执行action。表示回调执行指定name的action
  • Object类型: 回调执行action。该对象为action的提交选项。其中需要指定action字段,可选的root字段表示是否进行全局提交,其他字段为合法的提交选项。
  • Function类型:回调执行函数。第一个参数为请求的返回结果。
  • Array类型: 回调执行集合,集合中的元素可以是以上任一合法的类型。Vapi将自动按顺序执行。

resovledrejected参数可以分别在Vapi实例选项指定、action选项指定、action提交参数指定。v-api自动使用contact方法进行合并。

const posts = Vapi({
    baseURL: '/v1/client',
    state: { posts: [] },
    // 每个action均会在请求失败时执行全局的message action。root参数表示该action为全局action。
    rejected: { action:'message', root: true }
})
    .get({
        action: 'getPost', // action name
        property: 'post', // state property name
        path: ({ id }) => `/posts/${id}`
    })
    .get({
        action: 'listPosts',
        property: 'posts',
        path: '/posts'
    })
    .post({
        action: 'updatePost',
        property: 'post',
        path: ({ id }) => `/posts/${id}`,
        // 更新成功则,执行全局的message action。并且重新刷新posts列表
        resolved: [{ action: 'message', root: true }, 'listPosts']
    })
    .getStore(); // create store object

// with mapActions
this.updatePost({
    resolved: () => this.btnEnable = true, // 执行成功按钮恢复可点击
    rejected: () => this.btnEnable = false // 执行过程中按钮不可点击
});

// updatePost 最终的resolved 和rejected
{
    ...
    resolved: [{ action: 'message', root: true }, 'listPosts', () => {}],
    rejected: [{ action:'message', root: true }, () => {}]
    ...
}

v-api的能力远不止于此,从API了解更多。

API

构造函数

# Vapi

  • 函数签名: constructor(options:Object):Vapi

  • 用法:

    const vapi = Vapi({ baseURL: '' })
    
    // 等效
    const vapi = new Vapi({ baseURL: '' })
    

    Vapi方法内部会隐式的使用new创建一个对象。可以传入一个实例选项对象。

Vapi实例选项

# axios

  • 类型: axios (instance)

  • 默认值: axios (instance)

  • 用法:

    import Axios from 'axios'
    import Vapi from 'v-api';
    
    const service = Axios.create({
        withCredentials: true,
        headers: { 'X-Requested-With': 'XMLHttpRequest' }
    });
    
    Vapi({
        axios: service
    })
    
    

    axios可以是任何有效的axios实例。可以自定义axios实例,或者其他能够返回Promise的Http请求工具。默认使用axios

# baseUrl

  • 类型: string

  • 用法:

    Vapi({
        baseURL: '/v1/client'
    })
    

    baseURL将传入axios实例。除了实例选项可以设置baseURLaction选项requestCongi也可以指定请求的baseURL

    如果这两个地方都没有设置,将使用axios默认的baseURL。优先权:request config base URL > baseURL > axios instance base URL

# namespaced

  • 类型: boolean

  • 默认值: false

  • 用法:

    Vapi({
        namespaced: true
    })
    

    指定该实例下的store是否具有命名空间,与Vuex namespaced意义相同。

# validateResponse

  • 类型: function

  • 函数签名: validateResponse(res: Response): boolean

  • 默认值:

    function validateResponse(res) {
        if (!res) return false;
        const { status, data } = res;
        const isServerOk = !data || (data.code ? parseInt(data.code, 10) === 200 : true);
        return status === 200 && isServerOk;
    }
    
  • 详细: v-api默认服务端REST API返回的结果形式如下:

    {
        code: number,
        data: any,
        msg: string
    }
    

    其中code与HTTP status意义相同,data为实际返回的结果,msg为额外的信息。REST采用此设计,主要在于服务端能够返回更多信息给前端,方便前端灵活处理。如果你的项目中,采用其他的结构,可以重写validateResponse以便决定请求成功或者失败。

  • 用法:

    function validateResponse(res) {
        if (!res) return false;
        const { status } = res;
        return status >= 200 && status < 300 ;
    }
    
    Vapi({
        validateResponse
    })
    

    v-api将使用新的方法来判断请求结果。

# meta

  • 类型: object

  • 用法:

    Vapi({
        namespaced: true
    })
    

    添加实例元数据,函数类型的headerspath能够访问它,被该实例的所有action共享。将与action选项中的metaaction提交参数中的meta进行合并。

# resolved

  • 类型: String|Object|Funtion|Array

  • 用法:

    const posts = Vapi({
        baseURL: '/v1/client',
        state: { posts: [] },
        // 每个action均会在请求成功时执行全局的message action。root参数表示该action为全局action。
        Resolved: { action:'message', root: true }
    })
    

    设置实例的resolved,用于设置请求成功的回调。被该实例的所有action共享,最终会被合并到action中。

    • String类型:表示回调执行一个action。该action只能是本实例storeaction。如果需要向全局提交action,或者为action传递参数,则使用对象形式。

    • Object类型:也用于回调执行action。形式如下:

    {
        // 只有action属性是必须的
        action: String, // action name
        [root]: boolean, // global dispatch
        // 可以添加其他任何action提交参数
        [data]: Object, // http data
        [params]: Object,// http params
        [... other action dispatch options]
        // 也可以添加除action提交参数以外的数据
        [.. other payload]
    }
    
    • Function类型:回调执行函数。函数参数为(Response, rest)response为请求返回结果,restaction提交时除提交参数以外的数据。
    this.updatePost({
        data : {
            ...
        },
        data1, // data1不属于action提交参数,将会被原样传送给回调函数
        data2,
        ...,
        resolved: (res, { data1, data2, ... }) => {} // 剩余数据将会被传递给回调函数
    })
    
    • Array类型:可包含以上任一类型,甚至嵌套的数组。

# rejected

  • 类型: String|Object|Funtion|Array

  • 用法:

    resolved相同,用于设置请求失败的回调。

# state

  • 类型: Object|Function

  • 默认值: {}

  • 用法:

    const posts = Vapi({
        baseURL: '/v1/client',
        state: { posts: [] }
    })
    

    设置store的默认statestate应该与action中的property对应。如果没有指定默认的state,则默认为null

    如果state为函数形式,则在实例初始化时被执行。函数应该返回一个纯对象。

    const posts = Vapi({
        baseURL: '/v1/client',
        state: () => { return { posts: [] } } // 返回纯对象
    })
    

    注意:该函数并不是最终生成的state,如果需要最终生成的state为函数,请在store选项中指定。

    当请求成功时,将自动更新property对应的state

Vapi实例方法

# add

  • 函数签名: (options: Object): Vapi

  • 用法:

    add({
        action: 'listPosts',
        property: 'posts',
        path: '/posts',
        method: 'get'
    })
    

    Vapi实例上添加一个action,返回该实例。添加action并不是真正的Vuex action,这里添加了action的定义。可以通过选项对象设置action

    选项对象中action为必须字段。更多选项,请参考action选项

# get

  • 函数签名: (options: Object): Vapi

  • 用法:

    get({
        action: 'listPosts',
        property: 'posts',
        path: '/posts'
    })
    // 等同于
    add({
        action: 'listPosts',
        property: 'posts',
        path: '/posts',
        method: 'get'
    })
    

    add方法的别名,get请求的快捷方法。

# delete

  • 函数签名: (options: Object): Vapi

  • 用法:

    add方法的别名,delete请求的快捷方法。

# head

  • 函数签名: (options: Object): Vapi

  • 用法:

    add方法的别名,head请求的快捷方法。

# options

  • 函数签名: (options: Object): Vapi

  • 用法:

    add方法的别名,options请求的快捷方法。

# post

  • 函数签名: (options: Object): Vapi

  • 用法:

    add方法的别名,post请求的快捷方法。

# put

  • 函数签名: (options: Object): Vapi

  • 用法:

    add方法的别名,put请求的快捷方法。

# patch

  • 函数签名: (options: Object): Vapi

  • 用法:

    add方法的别名,patch请求的快捷方法。

# getStore

  • 函数签名: (options: Object): Store

  • 用法:

    Vapi().getStore({
        courseState: true, // 自动生成pending与error数据
        createStateFn: true // 返回的state为函数形式
    })
    

    用于生成Vuex store对象。

     {
         namespaced: false,
         state: {
             pending: {
                 posts: false,
                 post: false
             },
             error: {
                 posts: null,
                 post: null
             },
             posts: [],
             post: null
         },
         mutations: {
             LIST_POSTS: Function,
             LIST_POSTS_SUCCEEDED: Function,
             LIST_POSTS_FAILED: Function,
             GET_POST: Function,
             GET_POST_SUCCEEDED: Function,
             GET_POST_FAILED: Function,
             UPDATE_POST: Function,
             UPDATE_POST_SUCCEEDED: Function,
             UPDATE_POST_FAILED: Function
         },
    
         actions: {
             listPosts: Function,
             getPost: Function,
             updatePost: Function
         }
     }
    

action选项

# action

  • 类型: String

  • 用法:

     get({
         action: 'listPosts',
         property: 'posts',
         path: '/posts'
     })
    

    指定action的name,可通过Vuex store的dispatch进行提交。

    // direct via store
    this.$store.dispatch("actionName", { params: {}, data: {} })
    
    // or with mapActions
    this.actionName({ params: {}, data: {} })
    

# property

  • 类型: String

  • 用法:

    get({
        action: 'listPosts',
        property: 'posts',
        path: '/posts'
    })
    

    指定action对应的state字段。如果没有指定该字段,则不会自动更改state,即使指定了默认的state

    const posts = Vapi({
        baseURL: '/v1/client',
        state: { posts: [] }
    })
        .get({
            action: 'getPost', // action name
            property: 'post', // state property name
            path: ({ id }) => `/posts/${id}`
        })
        .get({
            action: 'listPosts',
            // property: 'posts', // 没有指定property
            path: '/posts'
        })
        .post({
            action: 'updatePost',
            property: 'post',
            path: ({ id }) => `/posts/${id}`
        })
        .getStore(); // create store object
    
    // post默认值为null,会自动设置post。
    // posts虽然设置了默认值,但是没有对应的property,因此不会自动改变。
    

    当仅仅需要添加一个普通的action,在此Store下并不需要state(如:全局提交),则可省略property,这很有必要。

# path

  • 类型: String|Function

  • 用法:

    Api请求的路径,如果path为相对路径,则会自动使用baseURL作为前缀。

    如果path为函数,则可使用meta元数据,path在发起请求前会自动执行。path应该返回字符串路径。

    详细使用方法参考在headers和path中使用参数

# headers

  • 类型: Object|Function

  • 用法:

    Api请求头部,该选项的值将于requestConfig选项中的headers合并。

    get({
            action: 'getPost', // action name
            property: 'post', // state property name
            headers: { userName: 'test' }
        })
    

    如果headers为函数,则可使用meta元数据,headers在发起请求前会自动执行。headers应该返回对象数据。

    详细使用方法参考在headers和path中使用参数

# method

  • 类型: String

  • 用法:

    Http请求的method字段。允许的值为:['get', 'delete', 'head', 'options', 'post', 'put', 'patch']

# requestConfig

  • 类型: Object

  • 用法:

    Axios的请求参数。该选项中指定的属性具有最高优先权。

# meta

  • 类型: object

  • 用法:

    添加action元数据,与Vapi实例选项用法一致,将与实例选项进行合并。

# resolved

  • 类型: String|Object|Funtion|Array

  • 用法:

    设置当前actionresolved,用于设置请求成功的回调。与Vapi实例选项用法一致,将与实例选项进行合并。

# rejected

  • 类型: String|Object|Funtion|Array

  • 用法:

    resolved相同,用于设置请求失败的回调。

# successHandler

  • 类型: Function

  • 参数: (state, payload)

  • 用法:

    用于设置请求成功时,覆盖默认的改变state的行为。见自定义请求处理方法

    第二个参数包含了接口的返回结果。如果在action提交时,添加了其他额外数据,这里也会被合并传入进来。

# errorHandler

  • 类型: Function

  • 参数: (state, payload)

  • 用法:

    第二个参数包含了接口的返回结果。如果在action提交时,添加了其他额外数据,这里也会被合并传入进来。

store选项

# createStateFn

  • 类型: boolean

  • 默认值: false

  • 用法:

    设置返回的state是否为函数形式。

    Vapi().getStore({
        courseState: true, // 自动生成pending与error数据
        createStateFn: true // 返回的state为函数形式
    })
    

    返回的store 为:

    {
        namespaced: false,
        state: function(){
            return {
                pending: {
                    posts: false,
                    post: false
                },
                error: {
                    posts: null,
                    post: null
                },
                posts: [],
                post: null
            };
        }
        ...
    }
    

    默认返回的state不被函数包裹。

# courseState

  • 类型: boolean

  • 默认值: true

  • 用法:

    设置是否默认生成pendingerror等数据。

    Vapi().getStore({
        courseState: false, // 不自动生成pending与error数据
        createStateFn: true// 返回的state为函数形式
    })
    

    返回的store 为:

    {
        namespaced: false,
        state: function(){
            return {
                posts: [],
                post: null
            };
        }
        ...
    }
    

# successSuffix

  • 类型: String

  • 默认值: SUCCEEDED

  • 用法:

    Vapi().getStore({
        successSuffix: 'REQUEST_OK'
    })
    

    设置mutations type的后缀。请求成功时,将于actionName一起作为mutation的type。这在手动触发mutation时,很有用。

# errorSuffix

  • 类型: String

  • 默认值: FAILED

  • 用法:

    设置mutations type的后缀。请求失败时,将于actionName一起作为mutation的type。同successSuffix

action提交参数

  • 类型: { [params: Object], [data: Object], [meta: OBject], [after: Funtion], [before: Funciton], [resolved:String|Object|Function|Array], [rejected:String|Object|Function|Array], [...any] }

  • 用法

    this.updatePost({
        resolved: () => this.btnEnable = true, // 执行成功按钮恢复可点击
        rejected: () => this.btnEnable = false, // 执行过程中按钮不可点击
        meta: { id : 123 }
    });
    

    提交action时,可以指定请求参数,或者传入来自页面的元数据,或者页面组件的回调等内容。

    这里可以传入afterbefore钩子函数,分别在请求发起之前与请求发起之后调用。函数签名为(error, data: any)

    其中第一个参数为error对象,第二个在after中是请求的返回结果。注意:这里请求成功或者失败都会被调用。

    注意after区别于resolvedrejected,虽然他们都能完成绝大部分相同的工作。但是after不管成功或失败都会调用,resolvedrejected只有在确定成功或失败才会分别调用。

    添加afterbefore钩子函数的好处在于,可以在请求开始前后做一些额外的操作,比如:更改页面的状态。

全局方法

# creatStore

  • 函数签名: (Vapi, options: Object): store

  • 用法

    import Vapi, { createStore } from 'v-api';
    
    const store = Vapi();
    createStore(store, {  createStateFn: true });
    

    Vapi实例的getStore调用了该方法,从Vapi实例创建一个store对象。

# mergeStore

  • 函数签名: (store, [store]): store

  • 用法

    import Vapi, { mergeStore } from 'v-api';
    
    const store = Vapi().getStore();
    mergeStore(store, { state:{}, mutations:{}, getters:{}, actions:{} });
    

    允许混入其他自定义的store。

开发步骤

# install dependencies
yarn

# build for production with minification
yarn run build

# run eslint and fix code
npm run lint

# run all tests
yarn run test

修改历史

see CHANGELOG.md

致谢

thanks for ...

For a detailed explanation on how things work, contact us www.389055604@qq.com.