Vue.js备忘记录(三)

一. 自定义指令 //操作DOM

有的情况下,你仍然需要对普通 DOM 元素进行底层操作,这时候就会用到自定义指令。

1.什么是自定义指令:

自己配置的指令

2.什么时候使用自定义指令?

当你不可避免的操作DOM时

3.如何全局注册指令

全局注册: 如果需要在多个组件中使用该指令,则应声明为全局

参数一:指令名

参数二:钩子函数

        Vue.directive('focus', {
	// 当被绑定的元素插入到 DOM 中时……
	inserted: function (el) {
	  // 聚焦元素
	  el.focus()
	}
  })

      

4.钩子函数

这里需要解释什么是钩子函数:

一个指令定义对象可以提供如下几个钩子函数 (均为可选):

  • bind一开始就执行 绑定阶段 ,在bind阶段 el无法拿到父元素 只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。
  • inserted一开始就执行 绑定后插入父元素中的阶段 在inserted阶段 el可以拿到父元素 被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)。
  • update组件数据更改时触发,其获取的是更新之前的DOM 所在组件的 VNode 更新时调用,但是可能发生在其子 VNode 更新之前。指令的值可能发生了改变,也可能没有。但是你可以通过比较更新前后的值来忽略不必要的模板更新 (详细的钩子函数参数见下)。
  • componentUpdated组件数据更改时触发,其获取的是更新之后的DOM.指令所在组件的 VNode 及其子 VNode 全部更新后调用。
  • unbind:只调用一次,指令与元素解绑时调用。

5.钩子函数参数

每个钩子函数都默认传入两个参数 el和binding

el是调用此函数的DOM元素, binding是 v-指令 对象

指令钩子函数会被传入以下参数:

  • el:指令所绑定的元素,可以用来直接操作 DOM 。
  • binding:一个对象,包含以下属性:
    • name:指令名,不包括 v- 前缀。
    • value:指令的绑定值,例如:v-my-directive="1 + 1" 中,绑定值为 2
    • oldValue:指令绑定的前一个值,仅在 updatecomponentUpdated 钩子中可用。无论值是否改变都可用。
    • expression:字符串形式的指令表达式。例如 v-my-directive="1 + 1" 中,表达式为 "1 + 1"
    • arg:传给指令的参数,可选。例如 v-my-directive:foo 中,参数为 "foo"
    • modifiers:一个包含修饰符的对象。例如:v-my-directive.foo.bar 中,修饰符对象为 { foo: true, bar: true }
  • vnode:Vue 编译生成的虚拟节点。移步 VNode API 来了解更多详情。
  • oldVnode:上一个虚拟节点,仅在 updatecomponentUpdated 钩子中可用。

6. 自定义指令函数简写

在很多时候,你可能想在bindupdate时触发相同行为,而不关心其它的钩子。

        Vue.directive('color-swatch', function (el, binding) {
  el.style.backgroundColor = binding.value
})

      

7. 使用

指令的名字前面加 v-来调用 ,

如果是驼峰命名法,则应该转为小写并用 - 连接

如 autoFocus 引用时 v-auto-focus

例如:使输入框聚焦

        Vue.directive('focus', {
  // 当被绑定的元素插入到 DOM 中时……
  inserted: function (el) {
    // 聚焦元素
    el.focus()
  }
})

      

8. v-指令:参数 = xxx 参数是如何传递的?

例如:v-bind:class='xxx' 这样的指令怎么定义?怎么把参数class传入 指令生成器中呢?

其实它是通过 第二个参数binding中的 arg传的

xxx怎么传入的呢?

其实它是通过 第二个参数binding中的value 传的

        <!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        [v-cloak] {
            display: none;
        }
    </style>
</head>

<body>
    <div id="app" v-cloak>
        <input type="text" name="" id="" v-my-bind:canshu1="msg">
    </div>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <script>
        Vue.directive('my-bind', {
            inserted: function (el,binding) {
                console.log(el);
                console.log(binding);
                console.log(binding.arg);
                console.log(binding.value);
            }
        })
        var vm = new Vue({
            data: {
                msg: 'hello'
            }
        }).$mount('#app')

    </script>
</body>

</html>

      

二. 组件化

  • template只能有一个根元素
  • template可以传值字符串,但是这样写没有高亮提醒😭
  • 可以用vue里单文件组件解决这一个问题 详见:单文件组件
  • 组件其实就是一个vue实例,所以他有自己的data methods等成员,也是一个独立的作用域
  • 但是,组件的data必须是个方法,方法返回一个对象作为组件的data
  • 组件可以被认为是js模块,所以存在组件间参数传递的问题

组件化思想就是将一个大视图拆分成一个个小的模块

组件化可以方便开发和维护,同时方便复用

组件系统是 Vue 的另一个重要概念,因为它是一种抽象,允许我们使用小型、独立和通常可复用的组件构建大型应用。仔细想想,几乎任意类型的应用界面都可以抽象为一个组件树:

v2-d54afdb5dbc7da830ff3f06b65b185bc_b.jpg

1.定义组件

在 Vue 里,一个组件本质上是一个拥有预定义选项的一个 Vue 实例。在 Vue 中注册组件很简单:

        // 定义名为 todo-item 的新组件
Vue.component('todo-item', {
  template: '<li>这是个待办项</li>'
})

var app = new Vue(...)

      

现在你可以用它构建另一个组件模板:

        <ol>
  <!-- 创建一个 todo-item 组件的实例 -->
  <todo-item></todo-item>
</ol>

      

2.全局组件和局部组件

  • 全局组件定义在全局,在任意组件中都可以直接使用
  • 局部组件定义在局部,只能在当前组件中使用
  • 建议把通用组件定义为全局,把不通用的涉及具体业务的组件定义为局部

(1).全局注册

        <!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <div id="app">
        <my-component></my-component>
    </div>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <script>
        Vue.component('my-component', {
            template:'<p>{{msg}}</p>',
            data:function(){
                return{
                    msg:'hello component!'
                }
            }
        })
        const app = new Vue({
            data: {
                message:'hello world',
            }
        }).$mount('#app')
    </script>
</body>

</html>

      

(2) 局部组件

首先明白子组件,子组件是声明在父组件的components属性内的组件,声明方法 如下:

                Vue.component('my-component1', {
            template: `
            <div>    
                <input type="checkbox" v-model="seen">{{msg}}
                <div :class="{box:seen}"></div>
                <component2></component2>
            </div>
            `,
            data: function () {
                return {
                    msg: 'hello component!',
                    seen:true
                }
            },
            components:{
                component2:{
                    template:"<h1>hello</h1>"
                }
            }
        })

      

这种声明方式使得component2只能在my-component1的作用域内被引用,无法被全局引用

3.组件的模板 template

4.传参的组件

上面的组件没有参数,生成的标签千篇一律,为了解决这个问题,我们可以给自己的组件传参

        Vue.component('todo-item', {
  // todo-item 组件现在接受一个
  // "prop",类似于一个自定义 attribute。
  // 这个 prop 名为 todo。
  props: ['todo'],
  template: '<li>{{ todo.text }}</li>'
})

      

这样,我们就可以生成不同内容的自定义组件了

例如:

        <div id="app-7">
  <ol>
    <!--
      现在我们为每个 todo-item 提供 todo 对象
      todo 对象是变量即其内容可以是动态的
      我们也需要为每个组件提供一个key”,稍后再
      作详细解释
    -->
    <todo-item
      v-for="item in groceryList"  
      v-bind:todo="item"
      v-bind:key="item.id"
    ></todo-item>
  </ol>
</div>

      


        Vue.component('todo-item', {
  props: ['todo'],
  template: '<li>{{ todo.text }}</li>'
})

var app7 = new Vue({
  el: '#app-7',
  data: {
    groceryList: [
      { id: 0, text: '蔬菜' },
      { id: 1, text: '奶酪' },
      { id: 2, text: '随便其它什么人吃的东西' }
    ]
  }
})

      

在一个大型应用中,有必要将整个应用程序划分为组件

        <div id="app">
  <app-nav></app-nav>
  <app-view>
    <app-sidebar></app-sidebar>
    <app-content></app-content>
  </app-view>
</div>
      

三. 父子组件

其实我们有一个组件通信大杀器:vuex,详见后续章节

组件的初衷就是配合使用,最常见的就是形成父子关系,A中用B,所以他们之间必须通讯,父组件要能够给子组件下发数据,子组件也可能要将内部的事件告知父组件

父子组件可以总结为prop向下传递,事件向上传递

1.props //给子组件传递数据

父组件通过prop给子组件下发数据

①.在父组件template中找到子组件,通过声明属性的方式传递数据

        <todo-body foo='bar'></todo-body>
      

②.在子组件中声明成员props:[]接收传递的参数 ,组件接收到的props数据后可以像访问data一样访问

        props: ['foo']
      

v2-e004b980428d744e4beeea209e4c8578_b.jpg

③.如果需要动态传递值,则:

        <todo-body v-bind:foo=todos></todo-body>
      

子组件中还是可以接收

        props:['todos']
      

2.单项数据流

prop是单项绑定的:当父组件的属性变化时,可以传递给子组件,反过来则不会,这种设计是为了放在子组件无意间改变父组件状态

在子组件中,确实可以操作父组件传入的数据,但只能操作引用类型的数据,这跟JS数据存储原理有关,但是,非常不建议这么做

普通类型不允许这样改, 实际上引用类型也不能用 = 号重新赋值

3.如何修改父组件数据

合理的想法:子组件应该把数据给父组件,父组件接收后自己修改自己的数据

那么,我们到底怎么修改父组件的数据呢?

① 在父组件中定义一个方法来修改自己的数据

        methods: {
			addTodo(titleText){
				this.todos.push({
					title:titleText,
					done:false
				})
			}
		},

      

②在子组件中发布一个事件,通知父组件:我可以去添加任务了

                    methods: {
                handleKeyUpEnter(e){
                    this.$emit('wantYouAddTodo', e.target.value)
                    this.title=''
                }
            },

      

③在父组件使用子组件的标签上 订阅 子组件发布的自定义事件

        <todo-header v-bind:todos=todos @wantYouAddTodo=addTodo></todo-header>
      

整个过程采用了发布/订阅的通信方式.请体会.

四. 组件化思想

1.原生Vue方法

index.html中没有任何标签,只留vue实例的入口标签,所有内容靠组件拼凑

        <!doctype html>
<html lang="en">

<head>
	<meta charset="utf-8">
	<meta name="viewport" content="width=device-width, initial-scale=1">
	<title>Template • TodoMVC</title>
	<link rel="stylesheet" href="node_modules/todomvc-common/base.css">
	<link rel="stylesheet" href="node_modules/todomvc-app-css/index.css">
	<!-- CSS overrides - remove if you don't need it -->
	<link rel="stylesheet" href="css/app.css">
</head>

<body>
	<div id="app"></div> 
	<!-- index只留一个App入口 -->
	<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
	<script src="components/todo-header.js"></script>
	<script src="components/todo-body.js"></script>
	<script src="components/todo-footer.js"></script>
	<script src="components/app-footer.js"></script>
	<script src="components/app.js"></script>
	<script src="main.js"></script>
	<!-- 所有js子组件按照先子后父的顺序引入 -->
</body>

</html>
      

根据入口<divid="app"></div> ,我们就会进入main.js,寻找到这个实例,

从某种意义上讲,main.js不算组件,是启动入口,我们把它放在根目录,而不放在components文件夹

        new Vue({   //这个vue实例对应着入口标签,接管了它
	el: '#app',
	template:'<App />', 
	//他的模板会替换掉<div id='app'></div>
	//<App />在哪定义的?原来是下面的子组件定义的
	components: {
		App
	}
})

      

顺理成章的.我们需要找到组件 App ,它定义在app.js中

        ; (function () {
	const template = `
	<div>
		<section class="todoapp">
			<todo-header></todo-header>
			<todo-body></todo-body>
			<todo-footer></todo-footer>
		</section>
		<app-footer></app-footer>
	</div>
	`
	//把template单独拿出来,更易于阅读
	//阅读上面的结构,我们发现,App里还是一个个自定义组件
	//为了让节点入口保持统一,以防将来操作DOM,我们给最外层div加了id为app
	window.App = {    
		//使用函数包裹,window调用是为了防止各个template命名冲突
		//现在这个template是window.App.template
		//相当于main.js调用的App是window提供的,
		//后续我们会有方法解决这个问题
		template,
		components: {
			TodoHeader,
			TodoBody,
			TodoFooter,
			AppFooter,
		}
		//这是App的子组件声明
	}
})()

      

于是,我们顺藤摸瓜找到了其他组件,它们和App组件结构相同.

整个项目的结构为:

v2-26825eaa0cbbb09f893755977c497fe6_b.jpg

2. 借用单文件页面系统

五. 路由状态切换写入生命周期

上一篇中的路由状态切换中,我们通过this.app将window.location.hash传递给

this指代的是window, 它里面有一个app实例.

改为组件化之后我们还有这个实例吗? 在调用时,我们可以把组件看成一个实例,但我们的组件文件里写的其实是组件的声明(类对象), 那我们怎么把window.location.hash传递给它?

这时候,我们需要把相对应的函数写进组件的生命周期之中

绑定在created周期是因为它是第一个拿到Vue组件的data的钩子

        		created() {
			window.onhashchange=()=>{
				this.filterText=window.location.hash
			}
		},  //这段代码写在todos-body组件中 需要app下发filterText

      

六. Element组件库

element是基于Vue的第三方组件库,他能帮我们更加快速的构建应用

The world's most popular Vue UI framework

安装

        cnpm i element-ui -S
      

或者CDN

目前可以通过 unpkg.com/element-ui 获取到最新版本的资源,在页面上引入 js 和 css 文件即可开始使用。

        <!-- 引入样式 -->
<link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css">
<!-- 引入组件库 -->
<script src="https://unpkg.com/element-ui/lib/index.js"></script>
      

举例:

        <!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <!-- 引入Vue -->
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <!-- 引入element样式 -->
    <link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css">
    <!-- 引入element组件库 -->
    <script src="https://unpkg.com/element-ui/lib/index.js"></script>
</head>

<body>
    <div id="app">
        <template>
            <div class="block">
              <span class="demonstration">请选择日期</span>
              <el-date-picker
                v-model="value1"
                type="date"
                placeholder="选择日期">
              </el-date-picker>
            </div>
          </template>
        <el-rate v-model="rate" show-text></el-rate>
        <el-button type="primary" @click=handleClick icon="el-icon-star-on">弹出评分</el-button>
    </div>
    <script>
        var vm = new Vue({
            data: {
                msg: 'hello',
                rate:4,
                value1:null,
                value2:null

            },
            methods: {
                handleClick(){
                    window.alert('当前评分:'+this.rate+'评分日期'+this.value1)
                }
            },
        }).$mount('#app')
    </script>
</body>

</html>
      

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值