热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

vue中的共享数据管理vuex

引言vuex是关于对vue中一些多个组件共享的数据进行统一集中式管理的方法。在实际的前端开发中,为了减少重复代码的使用,通常我们会把重复使用的代码&#x

 

引言

        vuex是关于对vue中一些多个组件共享的数据进行统一集中式管理的方法。在实际的前端开发中,为了减少重复代码的使用,通常我们会把重复使用的代码,封装成一个组件。不同组件之间一定会存在着数据共享传值的情况。如果数据传值频繁了,会很难控制数据的状态,很难统一协调维护。针对此情况,vue中诞生出了 vuex状态管理工具,react中是redux管理工具。vuex将全局管理共享的数据,统一集中式管理共享数据的状态。

        官宣:从vue思想角度考虑,状态管理包含三个部分: state、view、actions。state是驱动应用的数据源,view,以声明方式将state映射到视图,actions,响应在view上的用户输入导致的状态变化。     

        当我们的应用遇到多个组件共享状态时,单向数据流的简洁性很容易被破坏:

  • 多个视图依赖于同一状态
  • 来自不同视图的行为需要变更同一状态

        那么,问题来了,传参的方法对于多层嵌套的组件将会非常繁琐,并且对于兄弟组件间的状态传递无能为力。经常会采用父子组件直接引用或者通过事件来变更和同步状态的多份拷贝。

        针对上面的问题,vuex中的解决办法:把组件的共享状态抽取出来,以一个全局单例模式管理。在这种模式下,我们的组件树构成了一个巨大的“视图”,不管在树的哪个位置,任何组件都能获取状态或者触发行为。

        通过定义和隔离状态管理中的各种概念并通过强制规则维持视图和状态间的独立性,我们的代码将会变得更结构化且易维护。这就是vux背后的基本思想。

1. vuex

        Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。

        Vuex 可以帮助我们管理共享状态,并附带了更多的概念和框架。这需要对短期和长期效益进行权衡。如果开发大型单页应用,使用 Vuex 可能是繁琐冗余的,一个简单的 store 模式 (opens new window)就可以满足所需。如果要构建一个中大型单页应用,大有可能会考虑如何更好地在组件外部管理状态,Vuex 将会成为自然而然的选择。

      一个简单的store代码案例,如下

  1. 执行 ' vue create hello-world' 命令,创建一个 简单的 hello-world 项目框架,目录结构如下

    2. 不同文件中的内容如下:

Hello-world.vue



 

App.vue


main.js

import Vue from 'vue'
import App from './App.vue'
import store from './store'Vue.config.productionTip = falsenew Vue({render: h => h(App),store,components: { App },template: ''
}).$mount('#app')

store.js

import Vue from 'vue'
import Vuex from 'vuex'Vue.use(Vuex)let store = new Vuex.Store({state:{count:1}
})export default store

2.   核心概念

2.1  State

        Vuex使用单一状态树,用一个状态包含了全部的应用层级状态。每个应用仅仅包含一个store实例。单一状态树可以支持定位任一特定的状态片段,在调试过程中也能容易获取整个当前应用状态的快照。    

const counter = {template: `

{{ count }}
`,computed: {count () {return store.state.count}}
}

        每当 store.state.count 变化的时候,都会重新求取计算属性,并且触发更新相关联的DOM。这种模式导致组件全局状态单例,在模块化的构建系统中,在每个需要使用state的组件中需要频繁导入,并且在测试组件时需要模拟状态。

       Vuex通过 store选项,提供了一种机制将状态从根组件注入到每一个子组件中  Vue.use(Vuex)

const app = new Vue({el: '#app',// 把 store 对象提供给 “store” 选项,这可以把 store 的实例注入所有的子组件store,components: { Counter },template: `

`
})

      通过在根实例注册 store 选项,该 store 实例会注入到根组件下的所有子组件中,且子组件能通过this.$store访问到。让我们更新下Counter的实现

const Counter = {template: `

{{ count }}
`,computed: {count () {return this.$store.state.count}}
}

2.1.1  mapState辅助函数

        当一个组件需要获取多个状态的时候,将这些状态都声明为计算属性会有些重复和冗杂。

// 在单独构建的版本中辅助函数为 Vuex.mapState
import { mapState } from 'vuex'export default {// ...computed: mapState({// 箭头函数可使代码更简练count: state => state.count,// 传字符串参数 'count' 等同于 `state => state.count`countAlias: 'count',// 为了能够使用 `this` 获取局部状态,必须使用常规函数countPlusLocalState (state) {return state.count + this.localCount}})
}

        当映射的计算属性的名称与state的子节点名称相同时,我们可以给mapState传一个字符串数组。

computed: mapState([// 映射 this.count 为 store.state.count'count'
])

    对象展开运算符     mapstate函数返回的是一个对象。我们如何将它和局部计算属性混合使用?通常我们需要需要一个工具函数将多个对象合并为一个,以使我们可以将最终对象传给 computed属性。对象展开符可以简化这个写法

computed: {localComputed(){ //... },...mapstate({//...})
}

组件仍可以具有本地状态    使用Vuex并不意味着您将所有状态放入Vuex中。如果一个状态属于一个单一组件,那么将其保留为本地状态就可以了。在实际开发中,可以权衡是加入全局的Vuex中,还是单个组件的Vuex中。

2.2 Getters

        有时我们可能要根据存储状态来计算派生状态,例如,通过项目列表进行过滤并进行计数。

computed: {doneTodosCount(){return this.$store.state.todo.filter(todo => todo.done).length}
}

如果在多个组件中使用上面的状态函数,还需要复制多分放在不同的组件中。针对此,Vuex允许我们在store中定义 getters.,可以将其视为 store的计算属性,getter的结果根据其依赖关系进行缓存,并且仅在其依赖关系发生变更时,重新进行计算。

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)}}
})

2.2.1 Property-style Access

        getters 将其暴露在store.getters 对象上,可以作为属性直接访问

store.getters.doneTodos

getters还将接收其他getters作为第二个参数

getter: {doneTodosCount:(state,getters) => {return getters.doneTodos.length}
}

现在,我们可以在任何组件中使用它

computed: {doneTodosCount () {return this.$store.getters.doneTodosCount}
}

注意: 作为属性访问的getters在vuex生态系统中被缓存

2.2.2  Method-Style Access

    还可以通过返回函数将参数传递给 getter。当需要查询存储中的数组时,这特别有用

getters: {getTodoById: (state) => (id) => {return state.todos.find(todo => todo.id === id)}
}store.getters.getTodoById(2)

2.2.4 mapGetters helper mapGetters助手

        mapGetters助手可以简单地将 store中的 getters映射到本地计算属性中

import { mapGetters } from 'vuex';export default {computed: {...mapGetters(['doneTodosCount','anotherGetter'])}
}

        你还可以将 一个getter映射为不同的名字,当做一个对象

...mapGetters({doneCount:'doneTodosCount'
})

2.3  Mutations

        其实在Vuex store中,改变变化的状态,唯一的方法是通过传递一个mutations。Vuex mutations和事件非常相似:每一个mutation有一个type和一个handler。在handler函数中,可以操作实际的state变更,并且它接收这个state作为第一个参数

const store = new Vuex.Store({state: {count:1},mutations: {increment(state){state.count ++}}
})

    不能直接调用一个mutation handler。它看起来更像是事件注册。当一个mutation作为type increment被触发,这个handler被调用。为了触发一个mutation handler,你应该调用store.commit作为它的type

store.commit('increment')

Commit with Payload

可以向store.commit 传入额外的参数,即mutation的载荷。在大多数情况下,载荷应该是一个对象,这样可以包含多个字段并且记录的 mutation 会更易读:

mutations:{increment(state,n){state.count+=n;}
}store.commit('increment',10)

Object-Style Commit

提交mutation的另一种方式是直接使用包含  type 属性的对象

store.commit({type:'increment',amount: 10
})

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

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

Mutation 需遵守 Vue 的响应规则

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

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

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


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

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

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

        使用常量替代 Mutation 事件类型

        使用常量替代 mutation 事件类型在各种 Flux 实现中是很常见的模式。这样可以使 linter 之类的工具发挥作用,同时把这些常量放在单独的文件中可以让你的代码合作者对整个 app 包含的 mutation 一目了然。在需要多人协作的大型项目中,这会很有帮助。

// mutation-types.js
export const SOME_MUTATION = 'SOME_MUTATION'// store.js
import Vuex from 'vuex'
import { SOME_MUTATION } from './mutation-types'const store = new Vuex.Store({state: { ... },mutations: {// 我们可以使用 ES2015 风格的计算属性命名功能来使用一个常量作为函数名[SOME_MUTATION] (state) {// mutate state}}
})

Mutation 必须是同步函数

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

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

任何在回调函数中进行的状态的改变都是不可追踪的。

在组件中提交 Mutation

可以在组件中使用 this.$store.commit('xxx') 提交 mutation,或者使用 mapMutations 辅助函数将组件中的 methods 映射为 store.commit 调用

import { mapMutations } from 'vuex'export default {...mapMutations(['increment', // 将 `this.increment()` 映射为 `this.$store.commit('increment')`'incrementBy' // 将 `this.incrementBy(amount)` 映射为`this.$store.commit('incrementBy', amount)`]),...mapMutations([add:'increment'])
}

2.4  Action

    在 mutation 中混合异步调用会导致你的程序很难调试。例如,当你调用了两个包含异步回调的 mutation 来改变状态,你怎么知道什么时候回调和哪个先回调呢?在 Vuex 中,mutation 都是同步事务:

    Action 类似于 mutation,不同在于:

  • Action 提交的是 mutation,而不是直接变更状态。
  • Action 可以包含任意异步操作。

const store = new Vuex.Store({state: { count:0 },mutations: {increment(state){state.count ++}},actions: {increment (context){context.commit('increment')}}
})

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

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

actions: {increment({commit}){commit('increment')}
}

2.4.1 分发 Action

        Action可以通过 store.dispatch 方法触发

store.dispatch('increment')

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

actions: {incrementAsync({commit}){setTimeout(() => { commit('increment') },1000)}
}

        Actions 支持同样的载荷方式和对象方式进行分发:

store.dispathch('incrementAsync',{amount:10
})store.dispatch({type:'incrementAsybc',amount: 10
})

      复杂案例:涉及到调用异步 API 和分发多重 mutation:

actions:{checkout({ commit, state },products) {const savedCartItems = [...state.cart.added];commit(types.CHECKOUT_REQUEST)shop.buyProducts(products,() => commit(types.CHECKOUT_SUCCESS)() => commit(types.CHECKOUT_FAILURE,savedCartItems))}
}

2.4.2 在组件中分发 Action

        在组件中使用 this.$store.dispatch('xxx') 分发 action,或者使用 mapActions 辅助函数将组件的 methods 映射为 store.dispatch 调用(需要先在根节点注入 store)

import { mapActions } from 'vuex'export default {methods: {...mapActions(['increment', // 将 `this.increment()` 映射为 `this.$store.dispatch('increment')`'incrementBy' // 将 `this.incrementBy(amount)` 映射为 `this.$store.dispatch('incrementBy', amount)`]),...mapActions([add:'increment' // 将 `this.add()` 映射为 `this.$store.dispatch('increment')`])}
}

2.4.3 组合 Action

        Action 通常是异步的,action 结束后,处理异步流程。组合多个action,处理更加复杂的异步流程。

        store.dispatch 可以处理被触发的 action 的处理函数返回的 Promise,并且 store.dispatch 仍旧返回 Promise:

actions: {actionA ({ commit }) {return new Promise((resolve,reject) => {setTimeout(() => { commit('someMutation') },1000)})}
} store.dispatch('actionA').then(() => {}) ///或者actions: {actionB({ dispatch,commit }) {return dispatch('actionA').then(() => {commit('someOtherMutation')})}
}

    最后,如果我们利用 async / await (opens new window),我们可以如下组合 action:

actions: {aync actionA({ commit }){commit('goData',await getData())},async actionB ({ dispatch, commit }){await dispatch('actionA')commit('goOtherData', await getOtherData())}
}

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

2.5 Module

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

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

const moduleA = {state: () => ({ ... }),mutations: { ... },actions: { ... },getters: { ... }
}const moduleB = {state: () => ({ ... }),mutations: { ... },actions: { ... }
}const store = new Vuex.Store({modules: {a: moduleA,b: moduleB}
})store.state.a // -> moduleA 的状态
store.state.b // -> moduleB 的状态

2.5.1 模块的局部状态

        对于模块内部的 mutation 和 getter,接收的第一个参数是模块的局部状态对象

   

const moduleA = {state: () => ({count: 0}),mutations: {increment (state) {// 这里的 `state` 对象是模块的局部状态state.count++}},getters: {doubleCount (state) {return state.count * 2}}
}

  同样,对于模块内部的 action,局部状态通过 context.state 暴露出来,根节点状态则为 context.rootState:

const moduleA = {// ...actions: {incrementIfOddOnRootSum ({ state, commit, rootState }) {if ((state.count + rootState.count) % 2 === 1) {commit('increment')}}}
}

对于模块内部的 getter,根节点状态会作为第三个参数暴露出来:

const moduleA = {// ...getters: {sumWithRootCount (state, getters, rootState) {return state.count + rootState.count}}
}

2.5.2  命名空间

        默认情况下,模块内部的 action、mutation 和 getter 是注册在全局命名空间的——这样使得多个模块能够对同一 mutation 或 action 作出响应。

        如果希望你的模块具有更高的封装度和复用性,你可以通过添加 namespaced: true 的方式使其成为带命名空间的模块。当模块被注册后,它的所有 getter、action 及 mutation 都会自动根据模块注册的路径调整命名。例如:

const store = new Vuex.Store({modules: {acccount: {namespace: true,state: () => ({}),getters:{isAdmin() {} // -> getters['account/isAdmin']},actions: {login() {} // -> dispatch('account/login')},mutations: {login() {} // -> commit('account/login')},//嵌套模块modules: {myPage: {state: () => {},getters: {profile() {} // -> getters['account/profile']}},// 进一步嵌套命名空间posts:{namespaced: true,state: () => {},getters: {popular() {...} // -> getters['account/posts/popular']}}}}}
})

        启用了命名空间的 getter 和 action 会收到局部化的 getter,dispatch 和 commit。换言之,你在使用模块内容(module assets)时不需要在同一模块内额外添加空间名前缀。更改 namespaced 属性后不需要修改模块内的代码。

2.5.2  在带命名空间的模块内访问全局内容(Global Assets)       

        如果你希望使用全局 state 和 getter,rootState 和 rootGetters 会作为第三和第四参数传入 getter,也会通过 context 对象的属性传入 action。

        若需要在全局命名空间内分发 action 或提交 mutation,将 { root: true } 作为第三参数传给 dispatch 或 commit 即可。

modules: {foo: {namespaced: true,getters: {// 在这个模块的 getter 中,`getters` 被局部化了// 你可以使用 getter 的第四个参数来调用 `rootGetters`someGetter (state, getters, rootState, rootGetters) {getters.someOtherGetter // -> 'foo/someOtherGetter'rootGetters.someOtherGetter // -> 'someOtherGetter'},someOtherGetter: state => { ... }},actions: {// 在这个模块中, dispatch 和 commit 也被局部化了// 他们可以接受 `root` 属性以访问根 dispatch 或 commitsomeAction ({ dispatch, commit, getters, rootGetters }) {getters.someGetter // -> 'foo/someGetter'rootGetters.someGetter // -> 'someGetter'dispatch('someOtherAction') // -> 'foo/someOtherAction'dispatch('someOtherAction', null, { root: true }) // -> 'someOtherAction'commit('someMutation') // -> 'foo/someMutation'commit('someMutation', null, { root: true }) // -> 'someMutation'},someOtherAction (ctx, payload) { ... }}}
}

2.5.3 在带命名空间的模块注册全局 action

        若需要在带命名空间的模块注册全局 action,你可添加 root: true,并将这个 action 的定义放在函数 handler 中。例如:

{actions: {someOtherAction ({dispatch}) {dispatch('someAction')}},modules: {foo: {namespaced: true,actions: {someAction: {root: true,handler (namespacedContext, payload) { ... } // -> 'someAction'}}}}
}

2.5.4 带命名空间的绑定函数

        当使用 mapStatemapGettersmapActions 和 mapMutations 这些函数来绑定带命名空间的模块时,写起来可能比较繁琐:

computed: {...mapState({a: state => state.some.nested.module.a,b: state => state.some.nested.module.b})
},
methods: {...mapActions(['some/nested/module/foo', // -> this['some/nested/module/foo']()'some/nested/module/bar' // -> this['some/nested/module/bar']()])
}

        对于这种情况,你可以将模块的空间名称字符串作为第一个参数传递给上述函数,这样所有绑定都会自动将该模块作为上下文。

computed: {...mapState('some/nested/module',{a: state => state.a,b: state => state.b})
},method: {...mapActions('some/nested/module',{'foo','bar'})
}

        可以通过使用 createNamespacedHelpers 创建基于某个命名空间辅助函数。它返回一个对象,对象里有新的绑定在给定命名空间值上的组件绑定辅助函数:

import { createNamespacedHelpers } from 'vuex'const { mapState, mapAction } = createNamespacecdHelper('some/nested/module')export default {computed: {...mapState({a: state => state.a,b: state => state.b})},method: {...mapActions(['foo','bar'])}
}

        如果你开发的插件(Plugin)提供了模块并允许用户将其添加到 Vuex store,可能需要考虑模块的空间名称问题。对于这种情况,你可以通过插件的参数对象来允许用户指定空间名称

export function createPlugin(options ={}){return function(store){const namespace = options.namespace || '';store.dispatch(namespace + 'pluginAction')}
}

2.5.5模块动态注册

        在 store 创建之后,你可以使用 store.registerModule 方法注册模块:

import Vuex from 'vuex'const store = new Vuex.Store({})// 注册模块 `myModule`store.registerModule('myModule',{})// 注册嵌套模块 `nested/myModule`
store.registerModule(['nested','myNodule'],{})

        之后就可以通过 store.state.myModule 和 store.state.nested.myModule 访问模块的状态。

        模块动态注册功能使得其他 Vue 插件可以通过在 store 中附加新模块的方式来使用 Vuex 管理状态。例如,vuex-router-sync (opens new window)插件就是通过动态注册模块将 vue-router 和 vuex 结合在一起,实现应用的路由状态管理。

        使用 store.unregisterModule(moduleName) 来动态卸载模块。注意,你不能使用此方法卸载静态模块(即创建 store 时声明的模块)。

        可以通过 store.hasModule(moduleName) 方法检查该模块是否已经被注册到 store

2.5.6  保留state

        在注册一个新 module 时,你很有可能想保留过去的 state,例如从一个服务端渲染的应用保留 state。你可以通过 preserveState 选项将其归档:store.registerModule('a', module, { preserveState: true })

        当你设置 preserveState: true 时,该模块会被注册,action、mutation 和 getter 会被添加到 store 中,但是 state 不会。

2.5.7  模块重用

        有时我们可能需要创建一个模块的多个实例

  • 创建多个 store,他们公用同一个模块 (例如当 runInNewContext 选项是 false 或 'once' 时,为了在服务端渲染中避免有状态的单例 (opens new window))
  • 在一个 store 中多次注册同一个模块     

   如果使用一个纯对象来声明模块的状态,那么这个状态对象会通过引用被共享,导致状态对象被修改时 store 或模块间数据互相污染的问题。

        实际上这和 Vue 组件内的 data 是同样的问题。因此解决办法也是相同的——使用一个函数来声明模块状态(仅 2.3.0+ 支持)

const MyReusableModule = {state: ()=> {foo:'bar'},
}

3. 项目结构

        Vuex不限制代码结构。但是,它规定了一些需要遵守的原则

  1. 应用层级的状态应该集中到单个store对象中
  2. 提交 mutation 是更改状态的唯一方法,并且这个过程是同步的
  3. 异步逻辑都应封装到action里面

        如果 store 文件太大,只需将 action、mutation 和 getter 分割到单独的文件。

3.1 插件

store使用插件

const store = new Vuex.Store({// ...plugins: [myPlugin]
})

Vuex 的 store 接受 plugins 选项,这个选项暴露出每次 mutation 的钩子。Vuex 插件就是一个函数,它接收 store 作为唯一参数:

3.2 在插件内提交 Mutation        

        在插件中不允许直接修改状态——类似于组件,只能通过提交 mutation 来触发变化。

通过提交 mutation,插件可以用来同步数据源到 store。例如,同步 websocket 数据源到 store(下面是个大概例子,实际上 createPlugin 方法可以有更多选项来完成复杂任务)

export default function createWebSocketPlugin (socket) {return store => {socket.on('data', data => {store.commit('receiveData', data)})store.subscribe(mutation => {if (mutation.type === 'UPDATE_DATA') {socket.emit('update', mutation.payload)}})}
}

const plugin = createWebSocketPlugin(socket)const store = new Vuex.Store({state,mutations,plugins: [plugin]
})

3.3  生产 state快照

        有时候插件需要获得状态的“快照”,比较改变的前后状态。想要实现这项功能,你需要对状态对象进行深拷贝:

const myPluginWithSnapshot = store => {let prevState = _.cloneDeep(store.state)store.subscribe((mutation, state) => {let nextState = _.cloneDeep(state)// 比较 prevState 和 nextState...// 保存状态,用于下一次 mutationprevState = nextState})
}

          生成状态快照的插件应该只在开发阶段使用,使用 webpack 或 Browserify,让构建工具帮我们处理:

const store = new Vuex.Store({plugins: process.env.NODE_ENV !== 'production'? [myPluginWidthSnapshot]: []
})

        上面插件会默认启用。在发布阶段,你需要使用 webpack 的 DefinePlugin (opens new window)或者是 Browserify 的 envify (opens new window)使 process.env.NODE_ENV !== 'production' 为 false

3.2  内置Logger插件

        Vuex 自带一个日志插件用于一般的调试:

import createLogger from 'vuex/dist/logger'const store = new Vuex.Store({plugins:[createLogger]
})const logger = createLogger({collapsed: false,filter(mutation, stateBefore, stateAfter){return mutation.type !== 'BlocklistedMutation'},actionFilter (action, state) {// 和 `filter` 一样,但是是针对 action 的// `action` 的格式是 `{ type, payload }`return action.type !== "aBlocklistedAction"}, transformer (state) {// 在开始记录之前转换状态// 例如,只返回指定的子树return state.subTree},mutationTransformer (mutation) {// mutation 按照 { type, payload } 格式记录// 我们可以按任意方式格式化return mutation.type},actionTransformer (action) {// 和 `mutationTransformer` 一样,但是是针对 action 的return action.type},logActions: true, // 记录 action 日志logMutations: true, // 记录 mutation 日志logger: console, // 自定义 console 实现,默认为 `console`
})

 


推荐阅读
  • vue使用
    关键词: ... [详细]
  • Webpack5内置处理图片资源的配置方法
    本文介绍了在Webpack5中处理图片资源的配置方法。在Webpack4中,我们需要使用file-loader和url-loader来处理图片资源,但是在Webpack5中,这两个Loader的功能已经被内置到Webpack中,我们只需要简单配置即可实现图片资源的处理。本文还介绍了一些常用的配置方法,如匹配不同类型的图片文件、设置输出路径等。通过本文的学习,读者可以快速掌握Webpack5处理图片资源的方法。 ... [详细]
  • android listview OnItemClickListener失效原因
    最近在做listview时发现OnItemClickListener失效的问题,经过查找发现是因为button的原因。不仅listitem中存在button会影响OnItemClickListener事件的失效,还会导致单击后listview每个item的背景改变,使得item中的所有有关焦点的事件都失效。本文给出了一个范例来说明这种情况,并提供了解决方法。 ... [详细]
  • 本文讨论了一个关于cuowu类的问题,作者在使用cuowu类时遇到了错误提示和使用AdjustmentListener的问题。文章提供了16个解决方案,并给出了两个可能导致错误的原因。 ... [详细]
  • 本文介绍了在Linux下安装Perl的步骤,并提供了一个简单的Perl程序示例。同时,还展示了运行该程序的结果。 ... [详细]
  • 后台获取视图对应的字符串
    1.帮助类后台获取视图对应的字符串publicclassViewHelper{将View输出为字符串(注:不会执行对应的ac ... [详细]
  • 本文介绍了如何使用vue-awesome-swiper组件,包括在main.js中引入和使用swiper和swiperSlide组件,以及设置options和ref属性。同时还介绍了如何在模板中使用swiper和swiperSlide组件,并展示了如何通过循环渲染swipes数组中的数据,并使用picUrl属性显示图片。最后还介绍了如何添加分页器。 ... [详细]
  • VueCLI多页分目录打包的步骤记录
    本文介绍了使用VueCLI进行多页分目录打包的步骤,包括页面目录结构、安装依赖、获取Vue CLI需要的多页对象等内容。同时还提供了自定义不同模块页面标题的方法。 ... [详细]
  • 单页面应用 VS 多页面应用的区别和适用场景
    本文主要介绍了单页面应用(SPA)和多页面应用(MPA)的区别和适用场景。单页面应用只有一个主页面,所有内容都包含在主页面中,页面切换快但需要做相关的调优;多页面应用有多个独立的页面,每个页面都要加载相关资源,页面切换慢但适用于对SEO要求较高的应用。文章还提到了两者在资源加载、过渡动画、路由模式和数据传递方面的差异。 ... [详细]
  • 本文讨论了在dva中引入antd组件table时没有显示样式的问题。提供了.roadhogrc文件的配置,包括环境和import的设置。同时介绍了extraBabelPlugins和transform-runtime的使用方法,并解释了libraryName和css的含义。 ... [详细]
  • 在IDEA中运行CAS服务器的配置方法
    本文介绍了在IDEA中运行CAS服务器的配置方法,包括下载CAS模板Overlay Template、解压并添加项目、配置tomcat、运行CAS服务器等步骤。通过本文的指导,读者可以轻松在IDEA中进行CAS服务器的运行和配置。 ... [详细]
  • 1Lock与ReadWriteLock1.1LockpublicinterfaceLock{voidlock();voidlockInterruptibl ... [详细]
  • 在加载一个第三方厂商的dll文件时,提示“找不到指定模块,加载失败”。由于缺乏必要的技术支持,百思不得期间。后来发现一个有用的工具 ... [详细]
  • 由于同源策略的限制,满足同源的脚本才可以获取资源。虽然这样有助于保障网络安全,但另一方面也限制了资源的使用。那么如何实现跨域呢,以下是实现跨域的一些方法。 ... [详细]
  • 本文整理了Java面试中常见的问题及相关概念的解析,包括HashMap中为什么重写equals还要重写hashcode、map的分类和常见情况、final关键字的用法、Synchronized和lock的区别、volatile的介绍、Syncronized锁的作用、构造函数和构造函数重载的概念、方法覆盖和方法重载的区别、反射获取和设置对象私有字段的值的方法、通过反射创建对象的方式以及内部类的详解。 ... [详细]
author-avatar
手机用户2502870863
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有