Coder Social home page Coder Social logo

Vue3学习 about dailyr-ecord HOT 9 OPEN

zmj0920 avatar zmj0920 commented on June 19, 2024
Vue3学习

from dailyr-ecord.

Comments (9)

zmj0920 avatar zmj0920 commented on June 19, 2024

什么时候用setup()函数方式,什么时候不用?

官网推荐,对于结合单文件组件使用的组合式 API,推荐通过 <script setup>

  1. setup直接以属性的方式写在script标签上
  2. 在Vue3中,在xxx.vue单文件组件中的<script setup></script>,会有一个setup标记,
  3. 只要标记了它,那就说明,在script标签代码块内就具备了vue3的语法环境,
  4. 可以使用Vue3中的一些特性,比如组合式API函数,ref,reactive等
  5. 可以在script中声明响应式数据状态,定义声明完后,可以直接在模板中使用
<template>
     <!--在模板中不用.value,它会被自动解析,直接可以使用变量名--->
     {{name}}
</template>
<script setup>
  import {ref} from "vue";
  let name = ref("itclanCoder");
  //在逻辑中,想要读取name的值,需要.value方式
  console.log(name.value);
</script>

setup()以函数配置选项方式

在vue3当中,它是向下兼容的,如果想要在vue2中体验vue3当中的一些新特性,
在vue3中新增了一setup的配置选项 其他情况下,都应该优先使用<script setup></script>语法

<template>
    <div>
        {{name}}
    </div>
</template>
<script>
  import { ref } from "vue";
  export default {
    // 其他配置选项
    data() {
      return {
        num: 1
      }
    },
    setup() {
      const name = ref('itclanCoder');
      // 在逻辑中访问,同样需要使用.value的方式访问
      console.log(name.value);

      //在vue3的配置里去读取vue2的属性
      console.log(this.num);
      // 声明的变量或函数,都需要暴露出去,否则在模板中使用,会不起作用
      return {
        name
      }
    }
  }
</script>

重点注意
[1]. 在setup()函数内定义声明的响应式数据变量或函数,都需要对外暴露出去,否则在模板中直接使用的话,是不起作用的
[2]. 在模板中可以直接使用定义的响应式数据变量,或函数,因为它可以自动解析,而在逻辑中需要使用变量.value读取
[3]. setup()函数自身并不具备对组件实例的访问权限,所以在setup()函数作用域内,访问this会是undefined,但在选项式API中可以访问组合式API对外暴露出来的值,反过来却不行,也就是在setup()中无法通过this访问选项式API配置选项下的data数据或methods方法
[4]. 当选项式API中的data数据,methods方法名与setup()内定义的变量名或函数同名时,后者会覆盖前者
[5]. 凡是组合中用到数据,方法,均要配置在setup中,并且需要通过return对外暴露出去
[6]. setup函数有两种返回值,一个是对象,另一个是函数
[7]. 如果setup()函数返回一个对象,则对象中的属性,方法,在模板中可以直接使用,但在逻辑内,普通变量需要使用.value访问读取,若返回一个渲染函数,则可以自定义渲染内容
[8]. setup不能是一个async函数,因为返回值不在是return的对象,而是propmise,这样会令,模板看不到return对象中属性(其实也可以返回一个Promises实例,但需要结合Suspense和异步组件配合进行使用)

setup()是在什么时候执行的?

setup()是Vue提供的一个钩子,它的执行时机是在beforeCreate()函数之前执行的,在setup()函数里面访问this是undefined

总结

setup是vue3新增的一个特性,有两种使用,一种是直接写在script标签上,此时script标签代码块内可写vue3的新特性
而在选项式API编码风格当中,若想要使用vue3,那么需要借用setup()这配置选项,所有的组合响应式API函数,响应式变量,
都需要放在setup()函数里面同时,需要对外暴露出去
两种方式都是可以使用的,不过在官方推荐的使用当中,应当优先使用setup放在script标签上,
当需要基于选项式API的组合中,基于组合式API的代码时或第三方库整合的项目中,
或非单文件组件中使用组合式API时,可以用setup()函数。

from dailyr-ecord.

zmj0920 avatar zmj0920 commented on June 19, 2024

ref和reactive异同

  1. ref接收内部值返回响应式Ref对象,reactive返回响应式代理对象
  2. reactive内部采用的proxy,ref内部采用的是defineProperty
  3. 从定义上看ref通常用于处理单值的响应式,reactive用于处理对象类型的数据响应式
  4. 两者均是用于构造响应式数据,但是ref主要解决原始值的响应式问题
  5. ref返回的响应式数据在JS中使用需要加上.value才能访问其值,在视图中使用会自动脱ref,不需要.value;ref可以接收对象或数组等非原始值,但内部依然是reactive实现响应式;reactive内部如果接收Ref对象会自动脱ref;使用展开运算符(...)展开reactive返回的响应式对象会使其失去响应性,可以结合toRefs()将值转换为Ref对象之后再展开。
  6. reactive内部使用Proxy代理传入对象并拦截该对象各种操作(trap),从而实现响应式。ref内部封装一个RefImpl类,并设置get value/set value,拦截用户对值的访问,从而实现响应式。

ref

<template>
    <!--vue3的组件模版结构可以没有根标签-->
    <h1>我叫{{ name }}, {{ age }}岁</h1>
    <h3>职位:{{ job.type }}</h3>
    <h3>薪水:{{ job.salary }}</h3>
    <button @click="changeInfo">修改人的信息</button>
</template>
<script setup lang="ts">
import { ref } from 'vue';
//ref实现响应式(基本类型)也是采用Object.definedProperty()来实现的 getter和setter
let name = ref('小明');
let age = ref(21);
//ref实现响应式(对象类型)也是采用Proxy来实现
let job = ref({
    type: '前端',
    salary: 30000
});

function changeInfo() {
    name.value = '李四';
    age.value = 42;
    job.value.type = 'UI developer';
    console.log(name, age); //不是响应式的
}
// export default {
//     setup() {
//         //ref实现响应式(基本类型)也是采用Object.definedProperty()来实现的 getter和setter
//         let name = ref('小明');
//         let age = ref(21);
//         //ref实现响应式(对象类型)也是采用Proxy来实现
//         let job = ref({
//             type: '前端',
//             salary: 30000
//         });

//         function changeInfo() {
//             name.value = '李四';
//             age.value = 42;
//             job.value.type = 'UI developer';
//             console.log(name, age); //不是响应式的
//         }

//         //返回一个对象
//         return {
//             name,
//             age,
//             job,
//             changeInfo
//         }
//     }
// }
</script>

reactive

<template>
	<!--vue3的组件模版结构可以没有根标签-->
	<h1>我是app组件</h1>
	<h1>我叫{{ person.name }}, {{ person.age }}岁</h1>
	<h3>职位:{{ person.type }}</h3>
	<h3>薪水:{{ person.salary }}</h3>
	<h3>爱好:{{ person.hobbies }}</h3>
	<h4>测试的数据c:{{ person.a.b.c }}</h4>
	{{ a.b.c }}
</template>
  

<!-- <script lang="ts">
import { reactive, toRefs } from 'vue';
export default {
	setup() {
		let person = reactive({
			name: 'py',
			age: 21,
			type: 'frontend developer',
			salary: '30',
			hobbies: ['抽烟', '喝酒', '烫头'],
			a: {
				b: {
					c: 666
				}
			}
		});
		function changeInfo() {
			person.name = '李四';
			person.age = 42;
			// job.value.type = 'xxx'
			person.type = 'UI developer';
			//测试reactive能否监测深层次变化
			person.a.b.c = 100;
			person.hobbies[0] = 'play tennis';
		}

		//返回一个对象
		return {
			person,
			...toRefs(person), //当从组合式函数中返回响应式对象时,toRefs 解构/展开返回的对象而不会失去响应性:
			changeInfo
		}
	}
}
</script> -->

<script setup lang="ts">
import { reactive, toRefs } from 'vue';

let person = reactive({
	name: 'py',
	age: 21,
	type: 'frontend developer',
	salary: '30',
	hobbies: ['抽烟', '喝酒', '烫头'],
	a: {
		b: {
			c: 666
		}
	}
});
function changeInfo() {
	person.name = '李四';
	person.age = 42;
	// job.value.type = 'xxx'
	person.type = 'UI developer';
	//测试reactive能否监测深层次变化
	person.a.b.c = 100;
	person.hobbies[0] = 'play tennis';
	return {
		...toRefs(person), //当从组合式函数中返回响应式对象时,toRefs 解构/展开返回的对象而不会失去响应性:
	}
}
const { a } = changeInfo();
</script>

from dailyr-ecord.

zmj0920 avatar zmj0920 commented on June 19, 2024

watch和computed的区别以及选择?

  1. 先看两者定义,列举使用上的差异
  2. 列举使用场景上的差异,如何选择
  3. 使用细节、注意事项
  4. vue3变化

computed 表示计算属性, 通常用于处理数据, 方便在模板中的简化书写;
watch 表示监听,

在项目开发中, 当有一些数据需要处理: 随着其它数据变动而变动时,你很容易滥用 watch, 但是通常更好的做法是使用计算属性 computed, 而不是命令式的 watch 回调

使用过程中有一些细节,比如计算属性也是可以传递对象,成为既可读又可写的计算属性。watch可以传递对象,设置deep、immediate等选项。

vue3中watch选项发生了一些变化,例如不再能侦测一个点操作符之外的字符串形式的表达式; reactivity API中新出现了watch、watchEffect可以完全替代目前的watch选项,且功能更加强大。

computed

<template>
	<h1>一个人的信息</h1>
	姓:<input type="text" v-model="person.firstName" />
	<br />
	名:<input type="text" v-model="person.lastName" />
	<p>姓名:{{ fullName }}</p>
	<br />
	修改全名:<input type="text" v-model="fullName1" />
</template>
<script setup lang="ts">
import { reactive, computed } from 'vue';
let person = reactive({
	firstName: 'pan',
	lastName: 'yue',
	age: 21,
});

//计算属性(简写,没有考虑计算属性被修改的情况)
const fullName = computed(() => {
	return person.firstName + '-' + person.lastName;
})

//计算属性(完整写法,既考虑了读也考虑了改)
const fullName1 = computed({
	get() {
		return person.firstName + '-' + person.lastName
	},
	set(name) {
		let [fn, ln] = name.split('-');
		//响应式
		person.firstName = fn;
		person.lastName = ln;
	}
})
</script>

watch

<template>
	<h1>当前求和为:{{ sum }}</h1>
	<button @click="sum++">点我加一</button>
	<hr />
	<h2>当前的信息为:{{ msg }}</h2>
	<button @click="msg += '!'">修改信息</button>
	<hr />
	<h2>姓名:{{ person.name }}</h2>
	<h2>年龄:{{ person.age }}</h2>
	<h2>薪资:{{ person.job.j1.salary }}K</h2>
	<button @click="person.name = person.name + '~'">修改姓名</button>
	<button @click="person.age++">增长年龄</button>
	<button @click="person.job.j1.salary++">增长薪资</button>
</template>

<script lang="ts">
import { ref, reactive, watch } from 'vue';
export default {
	setup() {
		let sum = ref(0);
		let msg = ref('你好');
		let person = reactive({
			name: '张三',
			age: 18,
			job: {
				j1: {
					salary: 20
				}
			}
		})

		//情况一: 监视ref所定义的响应式数据
		watch(sum, (nv, ov) => {
		  //这里我并不需要this,所以剪头函数,普通函数我可以乱粥
		  console.log('sum的值发生变化了');
		  console.log(`newValue:${nv}, oldValue:${ov}`);
		}, {
		  //监视的配置
		  immediate: true //一上来就更新
		});

		//情况二:监视ref所定义的多个响应式数据
		// watch([sum, msg], (nv, ov) => {
		//   //此时nv和ov都是被监视属性值的数组
		//   // console.log(Array.isArray(ov)); //true
		//   console.log('sum的值或者msg的值发生变化了');
		//   console.log(`newValue:${nv}, oldValue:${ov}`);
		// },{
		//   immediate: true
		// });

		/**
		 * 情况三:监视reactive所定义的一个响应式数据
		 * 坑:1.此处无法获取正确的ov(oldValue)
		 *    2.强制开启了深度监视
		 */
		// watch(person, (nv, ov) => {
		//   console.log('person变化了');
		//   console.log(nv, ov);
		// }, {
		//   deep: false //此处的deep配置是无效的
		// });

		//情况四:监视reactive所定义的响应式中的某一个属性
		// watch(() => person.age, (nv, ov) => {
		//   console.log('person的age变了', nv, ov);
		// })

		//情况五:监视reactive所定义的响应式中的某些属性:并不只是一个
		// watch([() => person.age, () => person.name], (nv, ov) => {
		//   //此时nv和ov都是数组
		//   console.log('person的age或name发生改变了',nv, ov);
		// });

		//特殊情况
		// watch(() => person.job, (nv, ov) => {
		//   //这里依然无法拿到正确的ov,因为依然监视的是对象
		//   console.log('person的job信息发生改变了',nv, ov);
		// }, {
		//   //这里必须要加deep:true注意
		//   deep: true //此处因为监视的是reactive所定义的响应式对象的一个属性(这个属性的值它依然是一个对象),所以deep配置有效
		// })

		//返回一个对象
		return {
			sum,
			msg,
			person
		}
	}
}
</script>

from dailyr-ecord.

zmj0920 avatar zmj0920 commented on June 19, 2024

watch和watchEffect异同?

  • watchEffect立即运行一个函数,然后被动地追踪它的依赖,当这些依赖改变时重新执行该函数。watchEffect就是一种特殊的watch实现。

  • watch侦测一个或多个响应式数据源并在数据源变化时调用一个回调函数。

  • watchEffect立即运行一个函数,然后被动地追踪它的依赖,当这些依赖改变时重新执行该函数。

  • watch侦测一个或多个响应式数据源并在数据源变化时调用一个回调函数。

  • watchEffect(effect)是一种特殊watch,传入的函数既是依赖收集的数据源,也是回调函数。

  • 如果我们不关心响应式数据变化前后的值,只是想拿这些数据做些事情,那么watchEffect就是我们需要的。

  • watch更底层,可以接收多种数据源,包括用于依赖收集的getter函数,因此它完全可以实现watchEffect的功能,

  • 同时由于可以指定getter函数,依赖可以控制的更精确,还能获取数据变化前后的值,因此如果需要这些时我们会使用watch。

  • watchEffect在使用时,传入的函数会立刻执行一次。

  • watch默认情况下并不会执行回调函数,除非我们手动设置immediate选项。

  • 从实现上来说,watchEffect(fn)相当于watch(fn,fn,{immediate:true})

设置 flush: 'post' =>watchPostEffect 将会使侦听器延迟到组件渲染之后再执行。

在某些特殊情况下 (例如要使缓存失效),可能有必要在响应式依赖发生改变时立即触发侦听器。这可以通过设置 flush: 'sync' =>watchSyncEffect来实现。然而,该设置应谨慎使用,因为如果有多个属性同时更新,这将导致一些性能和数据一致性的问题。

watchEffect

<template>
	<h1>当前求和为:{{ sum }}</h1>
	<button @click="sum++">点我加一</button>
	<hr />
	<h2>当前的信息为:{{ msg }}</h2>
	<button @click="msg += '!'">修改信息</button>
	<hr />
	<h2>姓名:{{ person.name }}</h2>
	<h2>年龄:{{ person.age }}</h2>
	<h2>薪资:{{ person.job.j1.salary }}K</h2>
	<button @click="person.name = person.name + '~'">修改姓名</button>
	<button @click="person.age++">增长年龄</button>
	<button @click="person.job.j1.salary++">增长薪资</button>
</template>

<script lang="ts">
import { reactive, ref, watchEffect } from 'vue';
export default {
	setup() {
		let sum = ref(0);
		let msg = ref('你好');
		let person = reactive({
			name: '张三',
			age: 18,
			job: {
				j1: {
					salary: 20
				}
			}
		});
		//watchEffect
		//不确定监视对象
		//默认开启了immediate:true
		watchEffect(() => {
			console.log(`watch effect指定的回调执行了!!`)
			//依赖收集,你用到了谁它就监视谁!!
			//这里用到sum, person.job.j1.salary了,所以可以被监视到(只要它们发生变化就重新执行watchEffect)
			//与computed有点类似,依赖收集.(侧重点不一致,watchEffect注重过程,而computed注重计算函数的返回值)
			const x1 = sum.value;
			const x2 = person.job.j1.salary;
		})

		//返回一个对象
		return {
			sum,
			msg,
			person
		}
	}
}
</script>

watch

<template>
	<h1>当前求和为:{{ sum }}</h1>
	<button @click="sum++">点我加一</button>
	<hr />
	<h2>当前的信息为:{{ msg }}</h2>
	<button @click="msg += '!'">修改信息</button>
	<hr />
	<h2>姓名:{{ person.name }}</h2>
	<h2>年龄:{{ person.age }}</h2>
	<h2>薪资:{{ person.job.j1.salary }}K</h2>
	<button @click="person.name = person.name + '~'">修改姓名</button>
	<button @click="person.age++">增长年龄</button>
	<button @click="person.job.j1.salary++">增长薪资</button>
</template>

<script lang="ts">
import { ref, watch } from 'vue';
export default {
	setup() {
		let sum = ref(0);
		let msg = ref('你好');
		let person = ref({
			name: '张三',
			age: 18,
			job: {
				j1: {
					salary: 20
				}
			}
		});

		//监测的不是一个值,而是一个保存值的结构(不能写成sum.value) 不能监视一个具体的值注意
		watch(sum, (nv, ov) => {
			console.log(nv, ov);
		});

		console.log(person);

		//person是RefImpl
		//开启深度监视不会存在问题
		// watch(person, (nv, ov) => {
		//   console.log(nv, ov);
		// },{
		//   deep: true
		// });

		//这里如果不是person.value则会出现问题 因为person是一个RefImpl(默认没开启深度监视)
		//但是person.value是一个借助了proxy生成的reactive响应式对象 所以这里可以解决问题
		// watch(person.value, (nv, ov) => {
		//   console.log(nv, ov);
		// });

		console.log(sum);
		console.log(msg);
		console.log(person);

		//返回一个对象
		return {
			sum,
			msg,
			person
		}
	}
}
</script>

from dailyr-ecord.

zmj0920 avatar zmj0920 commented on June 19, 2024

readOnly和shallowReadOnly

readOnly接受一个对象 (不论是响应式还是普通的) 或是一个 ref,返回一个原值的只读代理。

只读代理是深层的:对任何嵌套属性的访问都将是只读的。它的 ref 解包行为与 reactive() 相同,但解包得到的值是只读的。
要避免深层级的转换行为,请使用 [shallowReadonly()]作替代。

<template>
	<h2>当前求和为:{{ sum }}</h2>
	<button @click="sum++">sum+1</button>
	<hr />
	<h2>姓名:{{ name }}</h2>
	<h2>年龄:{{ age }}</h2>
	<h2>薪资:{{ job.j1.salary }}K</h2>
	<button @click="name = name + '~'">修改姓名</button>
	<button @click="age++">增长年龄</button>
	<button @click="job.j1.salary++">增长薪资</button>
</template>

<script lang="ts">
import { ref, reactive, toRefs, readonly, shallowReadonly } from 'vue';
export default {
	setup() {
		let sum = ref(0);
		let person = reactive({
			name: '张三',
			age: 18,
			job: {
				j1: {
					salary: 20
				}
			}
		});

		// person = readonly(person); //此时person里面的属性值都不允许修改
		//person = shallowReadonly(person); //第一层不能改(name,age), 但j1和salary仍然可以改动
		// sum = readonly(sum); //同理
		// sum = shallowReadonly(sum)

		return {
			sum,
			...toRefs(person),
		};

	}
}
</script>

from dailyr-ecord.

zmj0920 avatar zmj0920 commented on June 19, 2024

readOnly和shallowReadOnly

readOnly接受一个对象 (不论是响应式还是普通的) 或是一个 ref,返回一个原值的只读代理。

只读代理是深层的:对任何嵌套属性的访问都将是只读的。它的 ref 解包行为与 reactive() 相同,但解包得到的值是只读的。
要避免深层级的转换行为,请使用 [shallowReadonly()]作替代。

<template>
	<h2>当前求和为:{{ sum }}</h2>
	<button @click="sum++">sum+1</button>
	<hr />
	<h2>姓名:{{ name }}</h2>
	<h2>年龄:{{ age }}</h2>
	<h2>薪资:{{ job.j1.salary }}K</h2>
	<button @click="name = name + '~'">修改姓名</button>
	<button @click="age++">增长年龄</button>
	<button @click="job.j1.salary++">增长薪资</button>
</template>

<script lang="ts">
import { ref, reactive, toRefs, readonly, shallowReadonly } from 'vue';
export default {
	setup() {
		let sum = ref(0);
		let person = reactive({
			name: '张三',
			age: 18,
			job: {
				j1: {
					salary: 20
				}
			}
		});

		// person = readonly(person); //此时person里面的属性值都不允许修改
		//person = shallowReadonly(person); //第一层不能改(name,age), 但j1和salary仍然可以改动
		// sum = readonly(sum); //同理
		// sum = shallowReadonly(sum)

		return {
			sum,
			...toRefs(person),
		};

	}
}
</script>

from dailyr-ecord.

zmj0920 avatar zmj0920 commented on June 19, 2024

响应式 API:工具函数

unref

省略ref调用值时的.value操作,直接进行数据的操作获取;
当使用.value太频繁的时候,不知道后面的值到底有没有.value,此时就可以用该API进行包裹访问

import { unref } from "vue";
let msgref = ref('你好')
console.log(unref(msgref)) // 你好
let  msg = '你好'
console.log(unref(msg)) // 你好

toRef

可以为源响应式对象上的某个属性新创建一个ref,且ref可以被传递,会保持对源属性的响应式连接


import { ref, toRef } from "vue";
// 使用 toRef 后 两个变量数据 会产生链式关系 互相响应 一个数据发送改变 另一个也会跟随 改变
const user = ref({
  name: "Lbxin",
  age: 22,
});

const newAge = toRef(user.value, "age");

newAge.value = 20;
console.log(user.value.age); // 20

user.value.age = 18;
console.log(newAge.value); // 18

from dailyr-ecord.

zmj0920 avatar zmj0920 commented on June 19, 2024

vue3生命周期

Vue生命周期总共可以分为8个阶段:创建前后, 载入前后, 更新前后, 销毁前后,以及一些特殊场景的生命周期。vue3中新增了三个用于调试和服务端渲染场景。
微信截图_20221228174912

beforeCreate:通常用于插件开发中执行一些初始化任务
created:组件初始化完毕,可以访问各种数据,获取接口数据等
mounted:dom已创建,可用于获取访问数据和dom元素;访问子组件等。
beforeUpdate:此时view层还未更新,可用于获取更新前各种状态
updated:完成view层的更新,更新后,所有状态已是最新
beforeunmounted:实例被销毁前调用,可用于一些定时器或订阅的取消
unmounted:销毁一个实例。可清理它与其它实例的连接,解绑它的全部指令及事件监听器

from dailyr-ecord.

zmj0920 avatar zmj0920 commented on June 19, 2024

defineComponent

defineComponent 是 Vue 3 推出的一个全新 API ,可用于对 TypeScript 代码的类型推导,帮助开发者简化掉很多编码过程中的类型声明。
defineComponent最重要的是:在TypeScript下,给予了组件 正确的参数类型推断 。

import { defineComponent } from 'vue'

// 使用 `defineComponent` 包裹组件的内部逻辑
export default defineComponent({
  setup(props, context) {
    // ...

    return {
      // ...
    }
  },
})

from dailyr-ecord.

Related Issues (9)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.