在uni中,想要实现页面通讯,比较主流的有5中方式,这4中方式各有各的优缺点,请谨慎选择。

  • 路由传参
  • globalData
  • 本地缓存
  • vuex
  • uni自带API方式

优缺点:
路由传参和本地缓存是长久存储,刷新页面数据不会丢失,但路由传参适用于数据比较少的情形。

globalData适用于小型项目,vuex适用于中大型项目。

1. 路由传参

适用场景:
适合于简单的页面传参,传递的参数比较少,如传递的比较多,推荐适用globalData、本地缓存、vuex。

A页面向B页面传递参数

uni.navigateTo({  
    url: 'test/test?id=1&name=张三'  
});

B页面接收A页面传递的参数

export default {  
    onLoad: function (option) { //option为object类型,会序列化上个页面传递的参数  
        console.log(option.id); //打印出上个页面传递的参数。  
        console.log(option.name); //打印出上个页面传递的参数。  
    }  
}

2. globalData

小程序有globalData,这是一种简单的全局变量机制。这套机制在uni-app里也可以使用,并且全端通用。

以下是 App.vue 中定义globalData的相关配置:

<script>  
    export default {  
        globalData: {  
            text: 'text'  
        }
    }  
</script>

js中操作globalData的方式如下: getApp().globalData.text = 'test'

3. 本地缓存

uni是一个跨平台的框架,可以编译运行到小程序、H5、APP等。
单纯的localstorage在uni中只能在H5端使用,如需在不同平台都能使用,得使用官方推荐得API。

3.1 uni.setStorage(OBJECT)

将数据存储在本地缓存中指定的 key 中,会覆盖掉原来该 key 对应的内容,这是一个异步接口。

OBJECT 参数说明

参数名 类型 必填 说明
key String 本地缓存中的指定的 key
data Any 需要存储的内容,只支持原生类型、及能够通过 JSON.stringify 序列化的对象
success Function 接口调用成功的回调函数
fail Function 接口调用失败的回调函数
complete Function 接口调用结束的回调函数(调用成功、失败都会执行)

示例

uni.setStorage({
    key: 'storage_key',
    data: 'hello',
    success: function () {
        console.log('success');
    }
});

3.2 uni.setStorageSync(KEY,DATA)

将 data 存储在本地缓存中指定的 key 中,会覆盖掉原来该 key 对应的内容,这是一个同步接口。

参数说明

参数 类型 必填 说明
key String 本地缓存中的指定的 key
data Any 需要存储的内容,只支持原生类型、及能够通过 JSON.stringify 序列化的对象
try {
    uni.setStorageSync('storage_key', 'hello');
} catch (e) {
    // error
}

3.3 uni.getStorage(OBJECT)

从本地缓存中异步获取指定 key 对应的内容。

OBJECT 参数说明

参数名 类型 必填 说明
key String 本地缓存中的指定的 key
success Function 接口调用的回调函数,res = {data: key对应的内容}
fail Function 接口调用失败的回调函数
complete Function 接口调用结束的回调函数(调用成功、失败都会执行)

success 返回参数说明

参数 类型 说明
data Any key 对应的内容

示例

uni.getStorage({
    key: 'storage_key',
    success: function (res) {
        console.log(res.data);
    }
});

3.4 uni.getStorageSync(KEY)

从本地缓存中同步获取指定 key 对应的内容。

参数说明

参数 类型 必填 说明
key String 本地缓存中的指定的 key

示例

try {
    const value = uni.getStorageSync('storage_key');
    if (value) {
        console.log(value);
    }
} catch (e) {
    // error
}

3.5 uni.getStorageInfo(OBJECT)

异步获取当前 storage 的相关信息。

平台差异说明

App H5 微信小程序 支付宝小程序 百度小程序
HBuilderX 2.0.3+

OBJECT 参数说明

参数名 类型 必填 说明
success Function 接口调用的回调函数,详见返回参数说明
fail Function 接口调用失败的回调函数
complete Function 接口调用结束的回调函数(调用成功、失败都会执行)

success 返回参数说明

参数 类型 说明
keys Array<String> 当前 storage 中所有的 key
currentSize Number 当前占用的空间大小, 单位:kb
limitSize Number 限制的空间大小, 单位:kb

示例

uni.getStorageInfo({
    success: function (res) {
        console.log(res.keys);
        console.log(res.currentSize);
        console.log(res.limitSize);
    }
});

3.6 uni.getStorageInfoSync()

同步获取当前 storage 的相关信息。

平台差异说明

App H5 微信小程序 支付宝小程序 百度小程序
HBuilderX 2.0.3+

示例

try {
    const res = uni.getStorageInfoSync();
    console.log(res.keys);
    console.log(res.currentSize);
    console.log(res.limitSize);
} catch (e) {
    // error
}

3.7 uni.removeStorage(OBJECT)

从本地缓存中异步移除指定 key。

OBJECT 参数说明

参数名 类型 必填 说明
key String 本地缓存中的指定的 key
success Function 接口调用的回调函数
fail Function 接口调用失败的回调函数
complete Function 接口调用结束的回调函数(调用成功、失败都会执行)

示例

uni.removeStorage({
    key: 'storage_key',
    success: function (res) {
        console.log('success');
    }
});

3.8 uni.removeStorageSync(KEY)

从本地缓存中同步移除指定 key。

参数说明

参数名 类型 必填 说明
key String 本地缓存中的指定的 key

示例

try {
    uni.removeStorageSync('storage_key');
} catch (e) {
    // error
}

3.9 uni.clearStorage()

清理本地数据缓存。

示例

uni.clearStorage();

3.10 uni.clearStorageSync()

同步清理本地数据缓存。

示例

try {
    uni.clearStorageSync();
} catch (e) {
    // error
}

注意

uni-app的Storage在不同端的实现不同:

  • H5端为localStorage,浏览器限制5M大小,是缓存概念,可能会被清理
  • App端为原生的plus.storage,无大小限制,不是缓存,是持久化的
  • 各个小程序端为其自带的storage api,数据存储生命周期跟小程序本身一致,即除用户主动删除或超过一定时间被自动清理,否则数据都一直可用。
  • 微信小程序单个 key 允许存储的最大数据长度为 1MB,所有数据存储上限为 10MB。
  • 支付宝小程序单条数据转换成字符串后,字符串长度最大200*1024。同一个支付宝用户,同一个小程序缓存总上限为10MB。
  • 百度、字节跳动小程序文档未说明大小限制

除此之外,其他数据存储方案:

  • H5端还支持websql、indexedDB、sessionStorage
  • App端还支持SQLiteIO文件等本地存储方案。

从HBuilderX2.6.6+起,App-Android平台对本地storage数据存储进行了性能优化,它的具体优化方式和升级注意事项,详见:Android平台 storage性能优化说明 - DCloud问答

4. VueX

uni-app 内置了 Vuex 。
使用 Vuex 需要遵守的规则:

  • 应用层级的状态应该集中到单个 store 对象中。

  • 提交 mutation 是更改状态的唯一方法,并且这个过程是同步的。

  • 异步逻辑都应该封装到 action 里面。

4.1 在uni中使用vuex:

  • 在 uni-app 项目根目录下,新建 store 目录,在此目录下新建 index.js 文件。在 index.js 文件配置如下:
// 页面路径:store/index.js
import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex);//vue的插件机制

//Vuex.Store 构造器选项
const store = new Vuex.Store({
    state:{//存放状态
        "username":"foo"
        "age":18
    }
})
export default store
  • 在 main.js 中导入文件。
<!-- 页面路径:main.js -->
import Vue from 'vue'
import App from './App'
import store from './store'

Vue.prototype.$store = store

// 把 store 对象提供给 “store” 选项,这可以把 store 的实例注入所有的子组件
const app = new Vue({
    store,
    ...App
})
app.$mount()

4.2 获取state

1.通过属性访问,需要在根节点注入 store 。

<!-- 页面路径:pages/index/index.vue -->
<template>
    <view>
        <text>用户名:{{username}}</text>
    </view>
</template>
<script>
    import store from '@/store/index.js';//需要引入store
    export default {
        data() {
            return {}
        },
        computed: {
            username() {
                return store.state.username 
            }
        }
    }
</script>

2.在组件中使用,通过 this.$store 访问到 state 里的数据。

<!-- 页面路径:pages/index/index.vue -->
<template>
    <view>
        <text>用户名:{{username}}</text>
    </view>
</template>
<script>
    export default {
        data() {
            return {}
        },
        computed: {
            username() {
                return this.$store.state.username 
            }
        }
    }
</script>

4.3 mapState

3.通过 mapState 辅助函数获取。

当一个组件需要获取多个状态的时候,将这些状态都声明为计算属性会有些重复和冗余。
为了解决这个问题,我们可以使用 mapState 辅助函数 帮助我们生成计算属性,让你少按几次键:

<!-- 页面路径:pages/index/index.vue -->
<template>
    <view>
        <view>用户名:{{username}}</view>
        <view>年龄:{{age}}</view>
    </view>
</template>
<script>
    import { mapState } from 'vuex'//引入mapState
    export default {
        data() {
            return {}
        },
        computed: mapState({
            // 从state中拿到数据 箭头函数可使代码更简练
            username: state => state.username,
            age: state => state.age,
        }) 
    }
</script>
  • 当映射的计算属性的名称与 state 的子节点名称相同时,我们也可以给 mapState 传一个字符串数组。
<!-- 页面路径:pages/index/index.vue -->
<template>
    <view>
        <view>用户名:{{username}}</view>
        <view>年龄:{{age}}</view>
    </view>
</template>
<script>
    import { mapState } from 'vuex'//引入mapState
    export default {
        data() {
            return {}
        },
        computed: mapState([
            'username',//映射 this.username 为 store.state.username
            'age',
        ])
    }
</script>
  • 为了能够使用 this 获取组件自己的data数据,必须使用常规函数。
<!-- 页面路径:pages/index/index.vue -->
<template>
    <view>
        <view>用户名:{{username}}</view>
        <view>年龄:{{age}}</view>
    </view>
</template>
<script>
    import { mapState } from 'vuex'//引入mapState
    export default {
        data() {
            return {
                firstName:"Li"
            }
        },    
        computed: {
            ...mapState({
                username: function (state) {
                    return this.firstName + ' ' +  state.username 
                },
                age: state => state.age,
            })
        },
    }
</script>
  • 使用对象展开运算符

mapState 函数返回的是一个对象。使用对象展开运算符将多个对象合并为一个,以使我们可以将最终对象传给 computed 属性。极大地简化写法:

<!-- 页面路径:pages/index/index.vue -->
<template>
    <view>
        <view>用户名:{{username}}</view>
        <view>年龄:{{age}}</view>
    </view>
</template>
<script>
    import { mapState } from 'vuex'//引入mapState
    export default {
        data() {
            return {}
        },
        computed: {
            //使用对象展开运算符将此对象混入到外部对象中
            ...mapState({
                username: state => state.username,
                age: state => state.age,
            })
        },
    }
</script>

4.4 Getter

可以认为是 store 的计算属性,对 state 的加工,是派生出来的数据。

  • 就像 computed 计算属性一样,getter 返回的值会根据它的依赖被缓存起来,且只有当它的依赖值发生改变才会被重新计算。
  • 可以在多组件中共享 getter 函数,这样做还可以提高运行效率。

在 uni-app 项目根目录下,store 目录 index.js 文件下:

<!-- 页面路径:store/index.js -->
import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex);

const store = new Vuex.Store({
    state: {
        todos: [{
                id: 1,
                text: '我是内容一',
                done: true
            },
            {
                id: 2,
                text: '我是内容二',
                done: false
            }
        ]
    },
    getters: {
        doneTodos: state => {
            return state.todos.filter(todo => todo.done)
        }
    }
})

export default store

在 store 上注册 gettergetter 方法接受以下参数:

  • state, 如果在模块中定义则为模块的局部状态
  • getters, 等同于 store.getters
<!-- 页面路径:store/index.js -->
import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex);

const store = new Vuex.Store({
    state: {
        todos: [{
                id: 1,
                text: '我是内容一',
                done: true
            },
            {
                id: 2,
                text: '我是内容二',
                done: false
            }
        ]
    },
    getters: {
        doneTodos: state => {
            return state.todos.filter(todo => todo.done)
        },
        doneTodosCount: (state, getters) => {
            //state :可以访问数据
            //getters:访问其他函数,等同于 store.getters
            return getters.doneTodos.length
        },
        getTodoById: (state) => (id) => {
            return state.todos.find(todo => todo.id === id)
        }
    }
})

export default store

4.5 获取getters

1.通过属性访问,Getter 会暴露为 store.getters 对象,你可以以属性的形式访问这些值。

<!-- 页面路径:pages/index/index.vue -->
<template>
    <view>    
        <view v-for="(item,index) in todos">
            <view>{{item.id}}</view>
            <view>{{item.text}}</view>
            <view>{{item.done}}</view>
        </view>
    </view>
</template>
<script>
    import store from '@/store/index.js'//需要引入store
    export default {
        computed: {
            todos() {
                return store.getters.doneTodos
            }
        }
    }
</script>

注意,getter 在通过属性访问时是作为 Vue 的响应式系统的一部分缓存其中的。

2.通过 this.$store 访问。

<!-- 页面路径:pages/index/index.vue -->
<template>
    <view>    
        <view v-for="(item,index) in todos">
            <view>{{item.id}}</view>
            <view>{{item.text}}</view>
            <view>{{item.done}}</view>
        </view>
    </view>
</template>
<script>
    export default {
        computed: {
            todos() {
                return this.$store.getters.doneTodos
            }
        }
    }
</script>

3.通过方法访问。

你也可以通过让 getter 返回一个函数,来实现给 getter 传参。在你对 store 里的数组进行查询时非常有用。

注意,getter 在通过方法访问时,每次都会去进行调用,而不会缓存结果。

<!-- 页面路径:pages/index/index.vue -->
<template>
    <view>
        <view v-for="(item,index) in todos">
            <view>{{item}}</view>
        </view>
    </view>
</template>
<script>
    export default {
        computed: {
            todos() {
                return this.$store.getters.getTodoById(2) 
            }
        }
    }
</script>

4.6 mapGetters

4.通过 mapGetters 辅助函数访问。

mapGetters 辅助函数仅仅是将 store 中的 getter 映射到局部计算属性:

<!-- 页面路径:pages/index/index.vue -->
<template>
    <view>
        <view>{{doneTodosCount}}</view>
    </view>
</template>
<script>
    import {mapGetters} from 'vuex' //引入mapGetters
    export default {
        computed: {
            // 使用对象展开运算符将 getter 混入 computed 对象中
            ...mapGetters([
                'doneTodos',
                'doneTodosCount',
                // ...
            ])
        }
    }
</script>

如果你想将一个 getter 属性另取一个名字,使用对象形式:

<!-- 页面路径:pages/index/index.vue -->
<template>
    <view>
        <view>{{doneCount}}</view>
    </view>
</template>
<script>
    import {mapGetters} from 'vuex' //引入mapGetters
    export default {
        computed: {
            ...mapGetters({
              // 把 `this.doneCount` 映射为 `this.$store.getters.doneTodosCount`
              doneCount: 'doneTodosCount'
            })
        }
    }
</script>

4.7 Mutation

Vuex中store数据改变的唯一方法就是mutation

通俗的理解,mutations 里面装着改变数据的方法集合,处理数据逻辑的方法全部放在 mutations 里,使数据和视图分离。

Vuex 中的 mutation 非常类似于事件:每个 mutation 都有一个字符串的 事件类型 (type) 和 一个 回调函数 (handler)。这个回调函数就是我们实际进行状态更改的地方,并且它会接受 state 作为第一个参数:

<!-- 页面路径:store/index.js -->
import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex);

const store = new Vuex.Store({
    state: {
        count: 1
    },
    mutations: {
        add(state) {
            // 变更状态
            state.count += 2
        }
    }
})
export default store

你不能直接调用一个 mutation handler。这个选项更像是事件注册:“当触发一个类型为 add 的 mutation 时,调用此函数”,要唤醒一个 mutation handler,你需要以相应的 type 调用 store.commit 方法。

注意store.commit 调用 mutation(需要在根节点注入 store)。

<!-- 页面路径:pages/index/index.vue -->
<template>
    <view>
        <view>数量:{{count}}</view>
        <button @click="addCount">增加</button>
    </view>
</template>
<script>
import store from '@/store/index.js'    
export default {
    computed: {
        count() {
            return this.$store.state.count
        }
    },
    methods: {
        addCount() {
            store.commit('add')
        }
    }
}
</script>

传入参数

你可以向 store.commit 传入额外的参数,即 mutation 的 载荷(payload):

还是以累加器的例子来实现 mutation 函数的传参,来动态定义累加的数量。

  • 在 mutation 传参(载荷)可以传递一个参数。
<!-- 页面路径:store/index.js -->
import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex);

const store = new Vuex.Store({
    state: {
        count: 1
    },
    mutations: {
        add(state, n) {
            state.count += n
        }
    }
})
export default store
<!-- 页面路径:pages/index/index.vue -->
<template>
    <view>
        <view>数量:{{count }}</view>
        <button @click="addCount">增加</button>
    </view>
</template>
<script>
    import store from '@/store/index.js'
    export default {
        computed: {
            count() {
                return this.$store.state.count
            }
        },
        methods: {
            addCount() {
                store.commit('add', 5)//每次累加 5
            }
        }
    }
</script>
  • 在 mutation 传参(载荷)可以也可以传递一个对象。让我们修改上面累加器的例子:
<!-- 页面路径:store/index.js -->
import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex);

const store = new Vuex.Store({
    state: {
        count: 1
    },
    mutations: {
        add(state, payload) {
            state.count += payload.amount
        }
    }
})
export default store
<!-- 页面路径:pages/index/index.vue -->
<template>
    <view>
        <view>数量:{{count }}</view>
        <button @click="addCount">增加</button>
    </view>
</template>
<script>
    import store from '@/store/index.js'
    export default {
        computed: {
            count() {
                return this.$store.state.count
            }
        },
        methods: {
            addCount () {//把载荷和type分开提交
                store.commit('add', { amount: 10 })
            }
        }
    }
</script>

提交方式

1.对象风格的提交方式

我们修改组件中 store.commit 提交方式:

<!-- 页面路径:pages/index/index.vue -->
<template>
    <view>
        <view>数量:{{count }}</view>
        <button @click="addCount">增加</button>
    </view>
</template>
<script>
    import store from '@/store/index.js'
    export default {
        computed: {
            count() {
                return this.$store.state.count
            }
        },
        methods: {
            addCount() {//整个对象都作为载荷传给 mutation 函数
                store.commit({
                    type: 'add',
                    amount: 6
                })
            }
        }
    }
</script>

当使用对象风格的提交方式,整个对象都作为载荷传给 mutation 函数,因此 handler 保持不变:

    mutations: {
        add(state, payload) {
            state.count += payload.amount
        }
    }

4.8 mapMutations

2.通过 mapMutations 辅助函数提交。

创建组件方法提交 mutation

使用 mapMutations 辅助函数将组件中的 methods 映射为 store.commit 调用(需要在根节点注入 store)。

<!-- 页面路径:pages/index/index.vue -->
<template>
    <view>
        <view>数量:{{count}}</view>
        <button @click="add">增加</button>
    </view>
</template>
<script>
    import { mapMutations } from 'vuex'//引入mapMutations
    export default {
        computed: {
            count() {
                return this.$store.state.count
            }
        },
        methods: {
            ...mapMutations(['add'])//对象展开运算符直接拿到add
        }
    }
</script>
<!-- 页面路径:store/index.js -->
import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex);

const store = new Vuex.Store({
    state: {
        count: 1
    },
    mutations: {
        add(state) {
            // 变更状态
            state.count += 2
        }
    }
})
export default store

遵守规则

既然 Vuex 的 store 中的状态是响应式的,那么当我们变更状态时,监视状态的 Vue 组件也会自动更新。这也意味着 Vuex 中的 mutation 也需要与使用 Vue 一样遵守一些注意事项:

  • 最好提前在你的 store 中初始化好所有所需属性。

  • 当需要在对象上添加新属性时,你应该

    • 使用 Vue.set(obj, 'newProp', 123), 或者

    • 以新对象替换老对象。例如,利用对象展开运算符我们可以这样写:

    state.obj = { ...state.obj, newProp: 123 }

Mutation 必须是同步函数

一条重要的原则就是要记住** mutation 必须是同步函数**

我们要通过提交 mutation 的方式来改变状态数据,是因为我们想要更明确地追踪到状态的变化。如果是类似下面这样异步的话:

    mutations: {
        someMutation (state) {
            api.callAsyncMethod(() => {
                state.count++
            })
        }
    }

我们就不知道什么时候状态会发生改变,所以也就无法追踪了,这与 mutation 的设计初心相悖,所以强制规定它必须是同步函数。

4.9 Action

action 类似于 mutation ,不同在于:

  • action 提交的是 mutation,通过 mutation 来改变 state ,而不是直接变更状态。
  • action 可以包含任意异步操作。

让我们来注册一个简单的 action :

<!-- 页面路径:store/index.js -->
import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex);

const store = new Vuex.Store({
    state: {
        count: 1
    },
    mutations:{
        add(state) {
            // 变更状态
            state.count += 2
        }
    },
    actions:{
        addCountAction (context) {
            context.commit('add')
        }
    }
})
export default store

action 函数接受一个与 store 实例具有相同方法和属性的 context 对象,因此你可以调用 context.commit 提交一个 mutation,或者通过 context.state 和 context.getters 来获取 state 和 getters

实践中,我们会经常用到 ES2015 的参数解构来简化代码(特别是我们需要调用 commit 很多次的时候):

    actions: {
        //参数解构
        addCountAction ({commit}) {
            commit('add')
        }
    }

分发 Action

1.actions 通过 store.dispatch 方法触发。

<!-- 页面路径:pages/index/index.vue -->
<template>
    <view>
        <view>数量:{{count}}</view>
        <button @click="add">增加</button>
    </view>
</template>
<script>
    import store from '@/store/index.js';
    export default {
        computed: {
            count() {
                return this.$store.state.count
            }
        },
        methods: {
            add () {
                store.dispatch('addCountAction')
            }
        }
    }
</script>
  • actions 支持以载荷形式分发:
<!-- 页面路径:store/index.js -->
import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex);

const store = new Vuex.Store({
    state: {
        count: 1
    },
    mutations:{
        add(state, payload) {
            state.count += payload.amount
        } 
    },
    actions:{
        addCountAction (context , payload) {
            context.commit('add',payload)
        }
    }
})
export default store
<!-- 页面路径:pages/index/index.vue -->
<template>
    <view>
        <view>数量:{{count }}</view>
        <button @click="add">增加</button>
    </view>
</template>
<script>
    import store from '@/store/index.js';
    export default {
        computed: {
            count() {
                return this.$store.state.count
            }
        },
        methods: {
            add () {
                // 以载荷形式分发
                store.dispatch('addCountAction', {amount: 10})
            }
        }
    }
</script>
  • actions 支持以对象形式分发:
    methods: {
        add () {
            // 以对象形式分发
            store.dispatch({
                type: 'addCountAction',
                amount: 5
            })
        }
    }

action 可以执行任意的同步和异步操作

我们可以在 action 内部执行异步操作:

<!-- 页面路径:store/index.js -->
import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex);

const store = new Vuex.Store({
    state: {
        count: 1
    },
    mutations:{
        add(state) {
            // 变更状态
            state.count += 2
        }
    },
    actions:{
        addCountAction (context) {
            //在执行累加的时候,会等待两秒才执行
            setTimeout(function () {
                context.commit('add')
            }, 2000)
        }
    }
})
export default store

来看一个更加实际的购物车示例,涉及到调用异步 API 和分发多重 mutation

    actions: {
        checkout ({ commit, state }, products) {
            // 把当前购物车的物品备份起来
            const savedCartItems = [...state.cart.added]
            // 发出结账请求,然后乐观地清空购物车
            commit(types.CHECKOUT_REQUEST)
            // 购物 API 接受一个成功回调和一个失败回调
            shop.buyProducts(
                products,
                // 成功操作
                () => commit(types.CHECKOUT_SUCCESS),
                // 失败操作
                () => commit(types.CHECKOUT_FAILURE, savedCartItems)
            )
        }
    }

注意我们正在进行一系列的异步操作,并且通过提交 mutation 来记录 action 产生的状态变更。

4.10 mapActions

2.通过 mapActions 辅助函数分发。

创建组件方法分发 action

  • 你在组件中使用 this.$store.dispatch('xxx') 分发 action
  • 或者使用 mapActions 辅助函数将组件的 methods 映射为 store.dispatch 调用(需要先在根节点注入 store )
<!-- 页面路径:pages/index/index.vue -->
<template>
    <view>
        <view>数量:{{count }}</view>
        <button @click="addCountAction">增加</button>
    </view>
</template>
<script>
    import { mapActions } from 'vuex'
    export default {
        computed: {
            count() {
                return this.$store.state.count
            }
        },
        methods: {
            ...mapActions([
                'addCountAction', 
                // 将 `this.addCountAction()` 映射为 `this.$store.dispatch('addCountAction')`
            ])
        }
    }
</script>
  • mapActions 也支持传入参数(载荷):
    methods: {
        ...mapActions([
            'addCountAction' 
            // 将 `this.addCountAction(amount)` 映射为 
            //`this.$store.dispatch('addCountAction', amount)`
        ]),
    }
  • mapActions 也支持传递一个对象:
    methods: {
        ...mapActions({
            addCount: 'addCountAction',
            // 将 `this.addCount()` 映射为 `this.$store.dispatch('addCountAction')`
        })
    }

组合 Action

action 通常是异步的,那么如何知道 action 什么时候结束呢?更重要的是,我们如何才能组合多个 action,以处理更加复杂的异步流程?

首先,你需要明白 store.dispatch 可以处理被触发的 action 的处理函数返回的 Promise,并且 store.dispatch 仍旧返回 Promise :

    actions: {
        actionA ({ commit }) {
            return new Promise((resolve, reject) => {
                setTimeout(() => {
                    commit('someMutation')
                    resolve()
                }, 1000)
            })
        }
    }

现在你可以在组件中使用:

    store.dispatch('actionA').then(() => {
        // ...
    })

在另外一个 action 中也可以:

    actions: {
        // ...
        actionB ({ dispatch, commit }) {
            return dispatch('actionA').then(() => {
                commit('someOtherMutation')
            })
        }
    }

最后,如果我们利用 async / await,我们可以如下组合 action :

    // 假设 getData() 和 getOtherData() 返回的是 Promise
    actions: {
        async actionA ({ commit }) {
            commit('gotData', await getData())
        },
        async actionB ({ dispatch, commit }) {
            await dispatch('actionA') // 等待 actionA 完成
            commit('gotOtherData', await getOtherData())
        }
    }

一个 store.dispatch 在不同模块中可以触发多个 action 函数。在这种情况下,只有当所有触发函数完成后,返回的 Promise 才会执行。

4.11 Module

由于使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,store 对象就有可能变得相当臃肿。

为了解决以上问题,Vuex 允许我们将 store 分割成模块(module)。每个模块拥有自己的 statemutationactiongetter、甚至是嵌套子模块——从上至下进行同样方式的分割:

1.在 store 文件夹下新建 modules 文件夹,并在下面新建 moduleA.js 和 moduleB.js 文件用来存放 vuex 的 modules 模块。

├── components             # 组件文件夹
    └── myButton 
        └── myButton.vue   # myButton组件
├── pages
    └── index 
        └── index.vue      # index页面
├── static
├── store
    ├── index.js          # 我们组装模块并导出 store 的地方
    └── modules           # 模块文件夹
        ├── moduleA.js    # 模块moduleA
        └── moduleB.js    # 模块moduleB
├── App.vue
├── main.js
├── manifest.json
├── pages.json
└── uni.scss

2.在 main.js 文件中引入 store

<!-- 页面路径:main.js -->
    import Vue from 'vue'
    import App from './App'
    import store from './store'

    Vue.prototype.$store = store

    // 把 store 对象提供给 “store” 选项,这可以把 store 的实例注入所有的子组件
    const app = new Vue({
        store,
        ...App
    })
    app.$mount()

3.在项目根目录下,新建 store 文件夹,并在下面新建 index.js 文件,作为模块入口,引入各子模块。

<!-- 页面路径:store/index.js -->
    import Vue from 'vue'
    import Vuex from 'vuex'

    import moduleA from '@/store/modules/moduleA'
    import moduleB from '@/store/modules/moduleB'

    Vue.use(Vuex)
    export default new Vuex.Store({
        modules:{
            moduleA,moduleB
        }
    })

4.子模块 moduleA 页面内容。

<!-- 子模块moduleA路径:store/modules/moduleA.js -->
export default {
    state: {
        text:"我是moduleA模块下state.text的值"
    },
    getters: {

    },
    mutations: {

    },
    actions: { 

    }
}

5.子模块 moduleB 页面内容。

<!-- 子模块moduleB路径:store/modules/moduleB.js -->
export default {
    state: {
        timestamp: 1608820295//初始时间戳
    },
    getters: {
        timeString(state) {//时间戳转换后的时间
            var date = new Date(state.timestamp);
            var year = date.getFullYear();
            var mon  = date.getMonth()+1;
            var day  = date.getDate();
            var hours = date.getHours();
            var minu = date.getMinutes();
            var sec = date.getSeconds();
            var trMon = mon<10 ? '0'+mon : mon
            var trDay = day<10 ? '0'+day : day
            return year+'-'+trMon+'-'+trDay+" "+hours+":"+minu+":"+sec;
        }
    },
    mutations: {
        updateTime(state){//更新当前时间戳
            state.timestamp = Date.now()
        }
    },
    actions: {

    }
}

6.在页面中引用组件 myButton ,并通过 mapState 读取 state 中的初始数据。

<!-- 页面路径:pages/index/index.vue -->
<template>
    <view class="content">
        <view>{{text}}</view>
        <view>时间戳:{{timestamp}}</view>
        <view>当前时间:{{timeString}}</view>
        <myButton></myButton>
    </view>
</template>
<script>
    import {mapState,mapGetters} from 'vuex' 
    export default {
        computed: {
            ...mapState({
                text: state => state.moduleA.text,
                timestamp: state => state.moduleB.timestamp
            }),
            ...mapGetters([
                'timeString'
            ])
        }
    }
</script>

7.在组件 myButton中,通过 mutations 操作刷新当前时间。

<!-- 组件路径:components/myButton/myButton.vue -->
<template>
    <view>
        <button type="default" @click="updateTime">刷新当前时间</button>
    </view>
</template>
<script>
    import {mapMutations} from 'vuex'
    export default {
        data() {
            return {}
        },
        methods: {
            ...mapMutations(['updateTime'])
        }
    }
</script>

vue是单向数据流,子组件不能直接修改父组件的数据,而通过vuex状态管理实现:把组件的共享状态抽取出来,以一个全局单例模式管理。在这种模式下,我们的组件树构成了一个巨大的“视图”,不管在树的哪个位置,任何组件都能获取状态或者触发行为!

5. uni自带API方式

5.1 $emit方法

uni.$emit(eventName,OBJECT)
触发全局的自定义事件。附加参数都会传给监听器回调。

属性 类型 描述
eventName String 事件名称
OBJECT Object 触发事件携带的附加参数

代码示例:

uni.$emit('update',{msg:'页面更新'})

5.2 $on方法

uni.$on(eventName,callback)
监听全局的自定义事件。事件可以由 uni.$emit 触发,回调函数会接收所有传入事件触发函数的额外参数。

属性 类型 描述
eventName String 事件名称
callback Function 事件的回调函数

代码示例

uni.$on('update',function(data){
    console.log('监听到事件来自 update ,携带参数 msg 为:' + data.msg);
})

参考链接:
https://uniapp.dcloud.io/tutorial/page.html#页面通讯

Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐