vue使用感受(一)组件间传值

2018-3-26

从上周开始,我开始接触了vue等前端框架,在此做一些总结,说说自己的感受。手头这个项目是PC端的一个管理系统。之前的开发中并没有使用前端框架,而是在一个流程引擎的基础上进行开发的。每一个流程都有许多表单,这些表单是由引擎自动渲染产生的,并没有html或jsp文件。如果要修改表单样式,需要每个表单对应一个js文件,在js文件中进行样式的修改。这个操作很傻,但是大佬们都表示没有办法。几百个表单,改到炸裂= =。

最近又提出要把现有的这个系统做一个移动端。原先的PC端系统是比较复杂的,直接做移动端适配肯定是不行的。所以一个想法就是借助前端框架来简化我这边的工作。我之前是久闻vue的大名,但是一直没有着手尝试过。于是我总共花了一周把项目样子搭出来了。然而后端大佬表示表单这部分还是不能提供接口,我表示很心塞。(我现在就在考虑如何让自己不改那几百个js)

话说回来,虽然表单这部分帮不上,vue在项目的其他部分是帮我简化了很多工作的。首先组件化这个我就很喜欢。因为我之前有做过一段时间的安卓开发,这种界面上分各个组件,组件复用等等很和我的胃口。其次,每一个vue中包含的template,script,style三部分,可以把项目中很多内容分隔开,不会互相产生影响。我不用建很多css文件和js文件,每个组件都十分独立(耦合度低?)。

前言

在这个项目中,我用了iView这个UI组件库,还是很方便的。推荐大家直接从iView的推荐工程开始入手,免去自己配置和调试。

https://github.com/iview/iview-project

接下来,我整理一下自己这一周遇到的一些问题以及我是如何解决的,希望有大佬能给点建议:

  1. 组件间传值
    1. 父组件向子组件
    2. 子组件向父组件
    3. 子组件间
  2. 组件间跳转
    1. vue-router
    2. 如何路由不变,部分组件跳转
  3. 样式覆盖
  4. 与后台接口交互

上面这些问题,基本上都可以百度找到答案。我就结合我自己实际的例子把它们汇总一下。至于现在还有什么问题没解决,就是那几百个表单了。由于篇幅原因,我把上面四个问题分为三次来聊。这次主要说说组件间的传值。 组件间传值

这个是遇到的第一个问题,而且你在项目的各个地方都有可能遇到。这边我就用我做的一个测试系统来解释一下。

image

我们先来看这个待办页面,它就包含了子组件向父组件、父组件向子组件两种传值。

父组件向子组件传值

我们可以把这个页面分成三部分:Header、Content、Footer。当然我这边还有一个侧边栏,暂时没它的事情就不提了。

image

Header和Footer在大多数界面都是不变的,主要在变化的就是Content。那么Content这部分我们可以做一个大组件。比如说待办这个页面的Content就是Todo.vue这个大组件。

image

在这个大组件中,我们又可以划分一些小组件。小组件的划分并不是必要的,也不用很细。我们只要考虑那些组件是我们在其他界面上可以复用的就行。比如说这里我只划分出一个自定义组件。

image

当然,这里用到很多iView的组件,比如说Form、Input、Card。自定义组件CustomListItem需要3个参数:标题、来源和日期。而这些参数我们需要通过Todo.vue这个父组件向后台接口请求数据之后,在一一添加给CustomListItem这个子组件。也就是父组件向子组件传值。这边我没有放入后台请求的部分,就只关注传值。

首先,是CustomListItem组件。

<template>
  <div class="listItem">
    <Row>
      <Col span="4">
        <Avatar icon="ios-paw" size="large"></Avatar>
      </Col>
      <Col span="20">
        <h3>{{title}}</h3>
        <div style="width:100%;">
          <p style="display:inline-block;">{{lastUser}}</p>
          <p style="display:inline-block;float:right;">{{time}}</p>
        </div>
      </Col>
    </Row>
    <hr style="margin:5px 0;">
  </div>
</template>
<style scoped>
.item-inline{
  display: inline-block;
}
.listItem{
  background: #fff;
}
</style>
<script>
export default {
  data () {
    return {}
  },
  props: ['title', 'lastUser', 'time']
}
</script>

我们把参数定义在props中,并在template中引用。接下来就是父组件Todo.vue。

<template>
  <div class="todo">
    <Form :model="searchForm" inline>
      <FormItem style="width:100%; padding:10px;margin:0;">
        <Input v-model="searchForm.input" icon="search" placeholder="请输入标题关键字" />
      </FormItem>
    </Form>
    <hr>
    <div style="overflow:auto;height:480px;">
      <div style="margin-bottom:10px;">
        <div class="listItemTitle">审片</div>
        <Card style="width:100%;" :bordered="false">
          <div @click="toDetail">
            <CustomListItem :title="itemTitle" :lastUser="itemLastUser" :time="itemTime"></CustomListItem>
          </div>
        </Card>
      </div>
    </div>
  </div>
</template>
<script>
import CustomListItem from '../children/CustomListItem'

export default {
  data () {
    return {
      title: '待办',
      searchForm: {
        input: ''
      },
      itemTitle: '流程标题',
      itemLastUser: '上级来源',
      itemTime: '日期时间'
    }
  },
  created() {
    this.$emit('setTitle', this.title)
  },
  components: {
    CustomListItem
  },
  methods: {
    toDetail () {
      //跳转到具体流程表单页面
      this.$router.push({ 
        name: 'detail'
      })
    }
  }
}
</script>
<style scoped>
.todo{
  background: #f5f7f9;
  overflow: auto;
}
</style>

这里的Todo.vue的data中定义了三个值itemTitle、itemLastUser、itemTime分别动态的和CustomListItem中的title、lastUser和time绑定。这样我们就可以把Todo.vue中的值传递过去了。

子组件向父组件传值

我们回到那个有Header、Content和Footer的界面。

image

Header这边主要有两个部分:按钮和标题。在这里这个标题是待办,但是在主页或者其他界面,应该要显示当前Content的标题。所以我们需要从Content这部分的组件向包含Header的这个父组件传值。也就是子组件向父组件传值。

首先是子组件还是刚才的Todo.vue。我们在created这个生命周期函数中使用了this.$emit()方法,向setTitle这个方法传递了title这个参数。

<template>
  <div class="todo">
    <Form :model="searchForm" inline>
      <FormItem style="width:100%; padding:10px;margin:0;">
        <Input v-model="searchForm.input" icon="search" placeholder="请输入标题关键字" />
      </FormItem>
    </Form>
    <hr>
    <div style="overflow:auto;height:480px;">
      <div style="margin-bottom:10px;">
        <div class="listItemTitle">审片</div>
        <Card style="width:100%;" :bordered="false">
          <div @click="toDetail">
            <CustomListItem :title="itemTitle" :lastUser="itemLastUser" :time="itemTime"></CustomListItem>
          </div>
        </Card>
      </div>
    </div>
  </div>
</template>
<script>
import CustomListItem from '../children/CustomListItem'

export default {
  data () {
    return {
      title: '待办',
      searchForm: {
        input: ''
      },
      itemTitle: '流程标题',
      itemLastUser: '上级来源',
      itemTime: '日期时间'
    }
  },
  created() {
    this.$emit('setTitle', this.title)
  },
  components: {
    CustomListItem
  },
  methods: {
    toDetail () {
      //跳转到具体流程表单页面
      this.$router.push({ 
        name: 'detail'
      })
    }
  }
}
</script>
<style scoped>
.todo{
  background: #f5f7f9;
  overflow: auto;
}
</style>

然后我们在父组件中用setHeader方法接收一下这个setTitle。(这个文件我删了很多方法,直接复制应该是跑不起来的)

<template>
  <div class="layout first">
    <Layout :style="{height: '100%'}">
        <Header :style="{padding: 0}" class="layout-header-bar">
          <Icon @click.native="collapsedSider" :class="rotateIcon" :style="{margin: '20px 20px 0'}" type="navicon-round" size="24"></Icon>
          <h2 style="display:inline-block">{{title}}</h2>
        </Header>
        <Content style="background:#f5f7f9;minHeight:260px;position:absolute;width:100%;top:64px;">
          <component v-bind:is="tabView" @setTitle="setHeader" @setPageByMain="setPageByMainPage"></component>
        </Content>
        <Footer style="position:absolute;bottom:0;width:100%;padding:0;background:#fff;">
          <BottomMenu style="height: 60px;" @setPageByMain="setPageByMainPage"></BottomMenu>
        </Footer>
    </Layout>
  </div>
</template>
<script>
import MainPage from './children/MainPage'
import BottomMenu from './children/BottomMenu'
import Todo from './menuPage/Todo'

export default {
  name: 'first',
  data () {
      return {
        tabView: 'MainPage',
        title: '',
      }
  },
  methods: {
    setHeader (data) {
      this.title = data
    }
  },
  components: {
    UserInfo,
    MainPage,
    BottomMenu,
    Todo
  }
}
</script>

至于这里的component组件我们会在之后的组件间跳转中提到。

子组件向子组件传值

子组件间传值需要通过一个类似于事件总线的东西来作为媒介。这个用到的地方比较少,就举个简单的例子。首先我们在src路径下创建一个bus.js的文件(文件名可以随便取。路径保证在src下级即可)。

import Vue from 'vue'

export default new Vue;

然后我们在子组件中引入bus.js,并在要传值的方法中通过bus来触发自定义的事件。

<template>
    <div class="msg">
      <label>
        <span>请输入值(子组件):</span>
        <input v-model="msg" />
      </label>
      <div id="emit">
        <Button type="primary" @click="bus">把值传递给另一个子组件</Button>
      </div>
    </div>
</template>
<script>
import Bus from '../bus.js'
export default {
  data () {
      return {
          msg: ''
      }
  },
  methods: {
    bus: function () {
      Bus.$emit('getFromBus', this.msg)
      console.log("child2触发了getFromBus")
    }
  }
}
</script>

然后在另一个子组件中接收。

<template>
  <div class="header">
      <div id="on">
        <h1>{{message}}</h1>
      </div>
  </div>
</template>
<script>
import Bus from '../bus.js'

export default {
  data () {
      return {
          message: ''
      }
  },
  created() {
    Bus.$on('getFromBus', (msg) => {
      console.log("child接收到了getFromBus")
      this.message = msg
    })
  }
}
</script>

以上就是vue组件间传值啦~后面还会聊一聊组件间跳转以及其他遇到的一些问题,敬请关注~