前言

在学习 Vue.js 组件化开发 Todo List 的时候,自己虽然也能编码实现,但如果不做笔记,只是写代码,学习的效果还不够好。只有把自己的实现思路记录下来,遇到的问题和解决方法也记录下来,用文字把这个过程梳理清楚,才能对整个项目有更加清晰、准确的认识。

注:该项目通过 vue-cli 搭建,GitHub 上的地址:todo-list。

TodoItem 组件

显示待办事项清单

先写一个最简单的组件,就是用 v-for 指令显示待办事项清单。数据也是用的本地的数据,这样在这一步能够把更多的精力放在学习组件的编写上。

首先,当然是在 components 目录下新建 TodoItem.vue 文件,用来显示待办事项清单,代码如下:

<template><ul><liv-for="task in tasks":key="task.id">{{ task.title }}</li></ul>
</template><script>
export default {name: 'TodoItem',props: {tasks: Array}
}
</script>

script 中,name 选项定义了组件的名称 TodoItemprops 选项则定义了组件所接收数据的名称 tasks 和类型:数组(Array)。

template 中,则在根元素 ul 内,通过 li 元素显示待办事项的名称 task.title。加了另一条语句 :key="task.id",是因为 Vue 建议在用 v-for 遍历时,为所遍历的每一项提供一个唯一的 key 属性(参考:key)。这一项不加也完全没关系,只不过 vue-cli 附带的 ESLint 会有错误提示,所以我这里就加上了。

另外这里还有个小知识点,Vue 规定组件的 template 中只能有一个根元素,也就是说下面这种写法是会报错的。个人猜测,之所以会有这种规定,也是为了最终渲染出来的 HTML 结构能更加清晰。仔细想想,这个理念也和组件化是相通的,不是嘛?

<!-- 错误写法 -->
<template><div></div><div></div>
</template>

这个组件最基本的内容已经写好了,接下来就在 App.vue 中引入它。

<script>
import TodoItem from "./components/TodoItem.vue";export default {name: "app",components: {TodoItem}
};
</script>

引入组件之后,当然还要为它提供数据,这样组件才有内容可以显示。这里也有个知识点,组件中的数据对象 data 必须是函数,因为这样能够保证组件实例不会修改同一个数据对象。刚开始写组件的时候可能容易忽略这个知识点,多写几次就记住了。

export default {name: "app",components: {TodoItem},data() {return {tasks: [{id: "6b9a86f6-1d1a-558a-83df-f98d84cd87bd",title: "JS",content: "Learn JavaScript",completed: true,createdAt: "2017-08-02"},{id: "1211bb33-a249-5782-bd97-0d5652438476",title: "Vue",content: "Learn Vue.js and master it!",completed: false,createdAt: "2018-01-02"}]};}
};

为组件准备好数据之后,就可以开始用它了。组件的基本用法也很简单,按照它的要求提供数据,然后组件就会按照自己设定的样式把数据显示出来。

<template><div id="app"><TodoItem:tasks="tasks" /></div>
</template>

上面的代码中,调用了 TodoItem 这个组件,并且将父组件中的数据属性 tasks 绑定到 TodoItem 这个组件的 props 选项上。在 :tasks="tasks" 这句代码中,等号前的 tasks 是子组件 TodoItem 中定义的名称,可以近似地理解为“形参”;等号后面的 tasks 则是父组件中的数据属性,可以近似地理解为“实参”。所以这种用法也可以理解成 :形参="实参",希望这种写法能够帮大家更容易地理解组件传入数据的语法。而父组件的数据属性和子组件的 props 选项都用 tasks 这个名称,是为了保持代码上的一致性,刚接触组件的时候可能觉得分不清谁是谁,但是代码写多了之后就能体会到这种写法的好处了,父组件只负责提供数据,子组件只负责使用数据,保持一致的命名,阅读和修改代码的时候就能很容易看出来互相之间的关系。

保存代码,然后在终端中执行 npm run serve,构建工具就会自动编译,然后在浏览器中打开页面,如果能够看到类似下图中的效果,就说明已经写好了一个最简单的组件了,接下来就要丰富这个 Todo List 的各项功能了。

样式改进

要使用 Bootstrap 的样式,首先需要把它的 CSS 文件引入进来,编辑 public 目录下的 index.html 文件,在 head 中加入下面的 CSS。后面需要引入 CSS 或者 JS 的时候,都可以在这里引入。当然了,也可以通过 npm install xxx 指令以后端库的形式引入,不过这样只能引入 JS,没法引入 CSS。不过有一天在火车上撸代码的时候,发现了以后端形式引入库的一个便利之处,就是它一旦安装好了,没有网络的情况下也完全可以正常用。

<!DOCTYPE html>
<html><head><link href="https://cdn.bootcss.com/bootstrap/4.0.0/css/bootstrap.min.css" rel="stylesheet"></head>
</html>

接下来就是搭框架,先修改 App.vue,确定整体框架:

<template><div id="app" class="container"><div class="col-md-8 offset-md-2 mt-5"><TodoItem:tasks="tasks" /></div></div>
</template>

在根 div 中加上 class="container",这样子元素就可以应用 col-md-8 这样的网格样式了。然后在子元素中加上 class="col-md-8 offset-md-2 mt-5"col-md-8 表示待办事项占12列宽度的网格中的8列,offset-md-2 表示往右偏移2列之后显示待办事项,这样就能够居中显示了。mt-5 则表示待办事项距离上方有一定空白,留白了才好看。

每个待办事项要显示标题、内容、日期,可以用 Bootstrap 的 Custom Content 列表。

观察上图对应的代码可以知道,a 标签内的 h5 标签可用于显示待办事项的标题,相邻的 small 标签可用于显示时间,a 标签内最后的 small 标签则可用显示于事项的具体内容,因此 TodoItem.vue 组件中可以改成如下内容。

<template><div class="list-group"><ahref="#"class="list-group-item list-group-item-action flex-column align-items-start"v-for="task in tasks":key="task.id"><div class="d-flex w-100 justify-content-between"><h5 class="mb-1">{{ task.title }}</h5><small>{{ task.createdAt }}</small></div><small>{{ task.content }}</small></a></div>
</template>

在浏览器中看看页面效果,怎么样,还不错吧?

从服务器获取数据

在实际业务中,数据都是放在服务器上,往往会在前端页面加载完成之后,再向服务器请求数据。这样前后端分离,让前端页面只关注界面部分,数据由后端负责提供,将前后端解耦,就降低了互相之间的依赖性。

要向服务器请求数据,可以用 axios 这个库,和前面引入 Bootstrap 的 CSS 一样,编辑 public 目录下的 index.html 文件,将 axios 这个库的链接加进来。

<!DOCTYPE html>
<html><head><script src="https://cdn.bootcss.com/axios/0.17.1/axios.min.js"></script></head>
</html>

然后再编辑父组件 App.vue,将数据属性 tasks 的初始值设置为空数组,在 Vue 实例的 created 这个生命周期钩子中获取数据。数据方面参考一个简单的 JSON 存储服务这篇文章的建议 ,放在 myjson 上。

const tasksUrl = "https://api.myjson.com/bins/xxxxx";export default {name: "app",components: {TodoItem},data() {return {tasks: []};},methods: {fetchData(jsonUrl, obj) {axios.get(jsonUrl).then(response => response.data).then(data => {data.forEach(ele => {obj.push(ele);});}).catch(console.log());},},created() {this.fetchData(tasksUrl, this.tasks);}
};

从上面的代码可以看到,数据属性的值保存在 tasksUrl 这个 URL 中,通过 axios 获取数据。在 Vue 中更新数组,需要用特定的变异方法,才能触发视图的更新,也就是上面代码中的 obj.push(ele)

另外,上面将更新数据部分的代码抽离成一个单独的函数 fetchData,这样能够提高代码的可读性。否则如果 created 这个钩子中需要执行五六个操作的时候,把具体的代码全放到这里面,那代码就乱得没法看了。

v-cloak 优化加载体验

为了优化用户体验,可以用 v-cloak 指令,实现组件在数据加载完成之后才显示的功能。

具体的测试结果,可以看视频:http://7xq4gx.com1.z0.glb.clouddn.com/v-cloak_fast-3g.mp4。

在上面这个视频中,通过 Chrome 开发者工具将网速限制为 "Fast 3G" 模式,以便更清楚地展示这个过程。然后点击刷新按钮加载页面,能够看到页面在成功获取到服务器上的数据之后,才会渲染组件内容并显示出来,在这之前页面则一直是空白状态。

TodoMenu 组件

显示菜单按钮

前面知道怎么用组件显示待办事项清单了,那么显示一个菜单列表也很容易了,照葫芦画瓢就行。

首先在父组件 App.vue 中准备数据 menus

export default {name: "app",components: {TodoItem,TodoMenu},data() {return {tasks: [],menus: [{ tag: "all", text: "全部" },{ tag: "doing", text: "未完成" },{ tag: "done", text: "已完成" }]};}
}

然后选择按钮的样式,自己选用了 Outline buttons,组件代码如下:

<template><div><buttontype="button"class="btn btn-outline-secondary"v-for="menu in menus":key="menu.id">{{ menu.text }}</button></div>
</template><script>
export default {name: 'TodoMenu',props: {menus: {type: Array,required: true}}
}
</script>

与之前编写 TodoItem 组件时相比,代码上主要的区别在于 props 的定义更加详细了,理由参见 Vue.js 官方文档中的风格指南:Prop 定义。

下面是当前的页面效果:

样式改进

基本的功能做出来了,接着来调整一下 TodoMenu 组件的样式,让它更好看一些。

首先是要给按钮之间加上间距,也是前面提到过的留白,就跟设计 PPT 一样,把页面塞得满满的其实很难看。查看 Bootstrap 的文档 Margin and padding,知道了可以用 mr-x 这样的类来设置右边距,测试了几个值之后,最终确定为 mr-2

然后还要给上面的一排按钮和下面的待办事项清单之间也加上间距,这里就用 mb-3 设置按钮的下边距,之前在 TodoItem 组件中设置的 mt-5 则删掉。

<template><div><buttontype="button"class="btn btn-outline-primary mr-2 mb-3"v-for="menu in menus":key="menu.id">{{ menu.text }}</button></div>
</template>

现在的页面效果就是这个样子的了:

网页加载完成后突出显示第一个按钮

查看 Bootstrap 的文档可以知道,给按钮添加一个 active 类,按钮就会处于被点击的状态。这样一来,只需要修改 menus 的数据结构,给每个对象添加一个名为 active 的布尔型变量,然后给 TodoMenu 组件动态绑定 active 类,就能实现页面加载完成后突出显示第一个按钮的功能了。

// App.vue
menus: [{ tag: "all", text: "全部", active: true },{ tag: "doing", text: "未完成", active: false },{ tag: "done", text: "已完成", active: false }
]
<!-- TodoMenu.vue 只列出了新增的部分 -->
<template><div><button:class="{active: menu.active}"></button></div>
</template>

突出当前被点击的按钮

除了要在网页加载完成后突出显示第一个按钮,还需要在用户点击各个按钮之后,突出显示用户所点击的按钮,这样能够让用户很清楚地看到自己所选中的是哪个按钮。

实现这个需求的流程如下(用了库 ramda):

  1. 查找 menusactive 属性为 true 的对象,也就是之前被点击的按钮对应的数据。
  2. 查找 menus 中当前被点击的按钮对应的对象:这个需要在子组件 TodoMenu.vue 中触发事件,将被点击的按钮所对应的数据(menu.tag)传递给父组件 App.vue,然后在父组件中查找该数据所对应的对象,如果和第一次查找的对象相同,说明前后两次点击了同一个按钮,那么就不用重复操作了。否则就需要把前一次点击的按钮的 active 属性设置为 false,然后将当前被点击的按钮的 active 属性设置为 true,这样就能够突出显示被点击的按钮了。

新增的代码如下:

<!-- index.html -->
<head><script src="https://cdn.bootcss.com/ramda/0.25.0/ramda.min.js"></script>
</head>
<!-- TodoMenu.vue -->
<template><div><button@click="activeButton(menu.tag)"></button></div>
</template><script>
export default {methods: {activeButton (tag) {this.$emit('active', tag);}}
}
</script>

上面是组件 TodoMenu.vue 新增的代码,用户点击按钮之后,会执行该组件内的 activeButton 函数。在函数中会触发 active 事件,并将当前按钮所对应对象的 tag 属性的值传给父组件。

<!-- App.vue -->
<template><div id="app"><div class="col-md-8 offset-md-2 mt-5"><TodoMenu:menus="menus"@active="activeButton" /></div>
</template><script>
export default {methods: {activeButton(tag) {let prevIndex = R.findIndex(R.propEq('active', true))(this.menus);let currIndex = R.findIndex(R.propEq('tag', tag))(this.menus);if (prevIndex !== currIndex) {this.menus[prevIndex].active = false;this.menus[currIndex].active = true;}}}
}
</script>

而上面的这段代码则是父组件 App.vue 中新增的代码,父组件监听到了子组件触发的 active 事件,就会执行父组件中的 activeButton 函数,对比两次点击的是否为同一按钮,然后根据结果执行对应的操作:如果点击的是不同的按钮,则将之前所点击的按钮对应的对象属性 active 值设置为 false,并将当前点击的按钮对应的对象属性的 active 的值设置为 true,Vue 监听到对象属性的变化,从而将类名动态绑定到 HTML 标签上,实现按钮的突出显示。

PS:自己之前的实现方案,是通过 jQuery 先将 menus 中所有对象的 active 属性设置为 false,然后用原生 JS 将触发了监听事件对象的 active 属性设置为 true,虽然代码也很简洁,但是代码的逻辑还是不如用 ramda 这个库的实现方式清晰。

点击按钮显示对应事项

这个需求可以在上一个需求的流程里完成,就是页面加载完成时,显示全部的待办事项;之后每次用户点击按钮,和前一次突出显示的按钮进行对比,如果相同,说明显示的还是那些待办事项,自然不用做什么操作;如果不同,那就显示按钮所对应分类的待办事项。

export default {data() {return {currTag: ""}},computed: {filteredTasks() {if (this.currTag === "all") {return JSON.parse(JSON.stringify(this.tasks));} else if (this.currTag === "doing") {return R.filter(task => task.completed === false)(this.tasks);} else if (this.currTag === "done") {return R.filter(task => task.completed === true)(this.tasks);} else {return null;}}},methods: {fetchData(jsonUrl, obj) {axios.get(jsonUrl).then(response => response.data).then(data => {data.forEach(ele => {obj.push(ele);});}).then((this.currTag = "all")).catch(console.log());}}
}

在上面的代码中,通过字符串属性 currTag 标记当前所点击的按钮,计算属性 filteredTaks 则根据 currTag 的值筛选出所要显示的待办事项。而在 fetchData 方法中,新增的 .then((this.currTag = "all")) 会在获取到数据之后设置所要显示的事项类别,这样整个流程就完整了。

上面这些只是功能上的变动,在界面部分也要对应调整,这样才能有更好的用户体验。具体来说,就是对于已完成的待办事项,复选框应为选中状态,并且文字的颜色要淡一些,这样才能和未完成的待办事项区分开来。

而实际的代码其实很简单,就是将传入组件的数据与 HTML 元素动态绑定:

<!-- 将 task.completed 属性与复选框的 checked 属性相绑定 -->
<inputtype="checkbox":checked="task.completed"><!-- 将 task.completed 与包含文字的 div 元素的 text-muted 这个类相绑定 -->
<divclass="col-md-11 d-flex w-100 justify-content-between":class="{'text-muted': task.completed}">
</div>

下面是调整好界面之后的效果图:

Todo Edit 组件

点击待办事项后显示编辑界面

首先设计编辑界面的基本样式,在这里用的是 Bootstrap 中的 Card 这个组件,这样可以把内部的元素都包裹到 card 中。待办事项的标题和内容显示在 textarea 元素中,待办事项的创建时间则显示在 card-footer 中。这个组件的代码如下所示:

<!-- TodoEdit.vue -->
<template><div class="card mt-3 mb-5"><div class="card-body"><div class="form-group"><textareaid="title"class="form-control font-weight-bold"rows="1"v-model="task.title"></textarea><textareaid="content"class="form-control mt-1"rows="3"v-model="task.content"></textarea></div></div><div class="card-footer text-muted">创建于:{{ task.createdAt }}</div></div>
</template><script>
export default {name: "TodoEdit",props: {task: {type: Object}}
}
</script>

从上面的代码中可以看到,将 idtitletextareatask.title 属性进行了双向绑定,idcontenttextarea 则与 task.content 属性进行了双向绑定,分别用来显示待办事项的标题和内容。

在父组件 App.vue 中,对象类型的数据属性 currTask 保存子组件 TodoEdit.vue 中所要显示的待办事项,并通过布尔类型的计算属性 renderEdit 决定是否要渲染子组件 TodoEdit.vue。在用户还没有点击待办事项的时候,还不需要渲染编辑界面,数据属性 currTask 还是个空对象,计算属性 renderEditfalse。在用户点击了某个待办事项之后,需要在编辑界面中显示数据属性 currTask 中的内容,计算属性 renderEdittrue,这样才会渲染子组件 TodoEdit.vue

父组件 App.vue 中新增的代码如下所示:

<!-- App.vue -->
<template><TodoEdit:task="currTask"v-if="renderEdit" />
</template><script>
export default {data() {return {currTask: {}}},computed: {renderEdit() {return Object.keys(this.currTask).length > 0 && this.currTask.constructor === Object;}},methods: {editTask(task) {this.currTask = JSON.parse(JSON.stringify(task));}}
}
</script>

从上面的代码可以看到,在页面及数据加载完成之后,用户点击待办事项之前,不会显示编辑界面。用户点击待办事项之后,将当前事项的信息保存至数据属性 currTask 中,计算属性 renderEdit 此时的值也为 true,便会渲染子组件 TodoEdit.vue,并将数据属性 currTask 的内容显示在子组件中。

完成之后的效果如下图所示:

样式改进

按照上面的方法完善代码之后,现在可以显示待办事项的编辑界面了。但是点击待办事项的话,浏览器地址栏中的地址会在最后附加上一个 # 字符:http://localhost:8080/#。如果不想有这种变化,那么就可以去掉 TodoItem.vue 组件的 href 属性,然后设置鼠标悬浮至该组件的 a 标签时显示手型指针即可:

<style scoped>
a:hover {cursor: pointer;
}
</style>

此外,由于 TodoEdit.vue 组件中,显示待办事项标题和内容用的都是 textarea 标签,而这个标签是可以通过拖动其右下角的标记来改变其大小的。但是对于待办事项而言,标题的文字数量一般都不多,不希望改变其大小,那么就要为这个标签进行单独的设置,设置其 resize 属性为 none 即可。

<style scoped>
#title {resize: none;
}
</style>

此时的效果如下所示:

切换编辑界面的显示状态

这个功能所要实现的效果,就是用户连续多次点击同一个待办事项时,编辑界面会在显示/隐藏两种状态之间来回切换,给用户以更好的使用体验。

最开始的思路:

  1. 新建数据属性 prevId 用于保存用户上一次点击的待办事项的 id 属性,并且将用户本次点击的待办事项的 id 属性与之进行对比。
  2. 如果两个属性值不同,说明前后两次点击的是不同的待办事项,就不需要隐藏编辑界面,并且将用户本次所点击的待办事项的 id 属性保存在 prevId 中,这样用户下一次再点击待办事项,就能与更新后的 prevId 属性进行对比。
  3. 如果两个属性值相同,说明两次点击的是同一个待办事项,那么 prevId 属性就没必要更新了,同时要切换编辑界面的显示状态。

从前面的代码可以知道,计算属性 renderEdit 的值决定了是否要渲染组件 TodoEdit.vue,数据属性 currTask 非空就会渲染。而用户首次点击待办事项之后,currTask 就永远都是非空的了,也就意味着编辑界面一直会被渲染。而这里需要实现的功能,是要让这个组件在显示/隐藏两种状态之间来回切换,需要注意的是,组件的“渲染”和“显示”是两回事,被渲染出来的组件,可以通过设置其 display 这个 CSS 属性的值为 false 来把它隐藏了。那么 Vue.js 中有没有类似的方式实现这个功能呢?当然有!那就是 v-show 指令。该指令后跟的表达式只要为真值,就会显示该元素,否则就会隐藏该元素。这不刚好就是我们需要的功能吗?这样一来,就可以通过优化代码逻辑,让上面新建的数据属性 prevId 来完成两件事:一方面这个数据属性可以用来保存每次点击的待办事项的 id 属性,另一方面还可以用它来决定是否要显示编辑界面。啊哈,一举两得,是不是很爽?另外,prevId 这个名称只是表示了它最原始的含义,其实可以重命名为 showEdit,用来表示它最终的业务逻辑,这样在阅读代码的时候就更容易理解了。下面就是优化后的代码逻辑:

  1. 页面加载完成,showEdit 为空。
  2. 首次点击某个事项,保存 idshowEdit
  3. 之后再点击同一个事项,idshowEdit 相同,则清空 showEdit
  4. 点击不同的事项,idshowEdit 不同,则更新至 showEdit 中。

流程已经很清楚了,代码自然也是水到渠成:

<template>
<TodoEditv-show="showEdit" />
</template><script>
export default {data() {return {showEdit: ""}},methods: {editTask(task) {// 仅列出该方法中新增的部分!this.showEdit? this.showEdit = task.id: this.showEdit === task.id? this.showEdit = '': this.showEdit = task.id;}}
}
</script>

俗话说优化无止境,上面的 editTask 方法中新增的代码,其实还可以进一步优化,不知道你有没有想到该如何优化呢?快动手试试吧!

点击“保存”按钮,保存更改

Header 组件

添加 Header 及文本内容

添加 Icon Font

Footer 组件

添加固定在底部的 Footer

参考资料

  • Collapsible contents (code block) in comments / spoiler tag · Issue #166 · dear-github/dear-github:用 Markdown 语法,实现内容的折叠效果。不过最后呈现出来的效果不好,就没有用上。

组件化 Todo List 编写笔记相关推荐

  1. android 组件化_Android 组件化路由框架设计(仿Arouter)

    前言 在组件化开发中一个必须要面对的问题就是组件间页面跳转,实现的方法有很多,简单的可以通过反射获取,但是比较耗费性能,也可以通过隐式跳转,但是随着页面的增多,过滤条件会随之增多,后期维护麻烦.那还有 ...

  2. android组建之间通信_Android组件化(三)组件之间的通信

    介绍 在组件化开发的时候,组件之间是相互独立的没有依赖关系,我们不能在使用显示调用来跳转页面了,因为我们组件化的目的之一就是解决模块间的强依赖问题,假如现在要从A业务组件跳转到业务B组件,并且要携带参 ...

  3. Vue学习笔记(2)(组件化开发)

    Vue2.x学习笔记.原视频教程:最全最新Vue.Vuejs教程,从入门到精通_哔哩哔哩 (゜-゜)つロ 干杯~-bilibili 组件化 1. 什么是组件化? 组件化是Vue.js中的重要思想 它提 ...

  4. 笔记|滴滴iOS客户端的架构,组件化,技术选型

    笔记来源infoq:滴滴iOS客户端的架构演变之路 1,状态机,把订单中的阶段,例如:出租车的等待抢单.出租车的等待接驾.专车的等待抢单.专车的等待接驾,都当成一种独立的状态,每 个状态机只需要知道可 ...

  5. 前端笔记:使用Web Components进行原生组件化开发

    文章目录 目的 基础说明 技术点介绍 Custom elements Shadow DOM HTML templates 整合成独立组件文件 示例演示与说明 示例一 示例二 es6-string-ht ...

  6. Android组件化架构实践,字节跳动学习笔记

    前言 2021全年从开头到结尾似乎就没多少好消息.这里我说的是整个互联网行业,并没有单单挑出某个公司或者某个细分领域.而对于广大Android开发者来说,找工作似乎也变得越来越难了. 2021年1月, ...

  7. Vue学习笔记(二)组件化和模块化

    Vue学习笔记(二)组件化和模块化 前言 组件化 什么是组件化 1.基础使用 2.全局组件和局部组件 3.语法糖和模板抽离 4.组件的data为什么是函数 5.父子组件 5.1 父子组件 5.2 父子 ...

  8. 阿瑶的Vue学习笔记(2)Vue 组件化编程

    2.非单文件组件 2.1 模块与组件.模块化与组件化 2.1.1模块 理解:向外提供特定功能的js程序,一般就是一个js文件 为什么:js文件很多很复杂 作用:复用js,简化js的编写,提高js运行效 ...

  9. 腾讯技术团队人手一份的Android组件化实战笔记(含得到、微信、美团、爱奇艺APP架构项目及源码)

    前言 以前的Android开发大多用的中心化管理思想,将相同的资源集中进行管理.随着项目的发展,弊端渐显:集中管理的资源越来越多,多人开发也越来越麻烦,时常牵一发而动全身. 尤其是在大公司或者是大项目 ...

最新文章

  1. python string 转bytes 以及bytes 转string
  2. Eclipse快捷键指南
  3. 请解释各种自动装配模式的区别?
  4. html标签属性和标签事件,HTML参考手册( 标签、属性和事件)
  5. 苹果面临5G困境!向三星采购碰壁 高通表态:苹果有我们电话
  6. 区块链教程Fabric1.0源代码分析配置交易-生成通道配置
  7. Mac vscode花屏问题解决
  8. 模态框 (框架引入出现的问题)
  9. 设计模式的C++实现 2.工厂模式
  10. Starting MySQL ERROR! Couldn't find MySQL server (/usr/local/mysql/bin/mysqld_safe)
  11. sqlite 表与表之间的关系_第33章 Django多表关系之一对一
  12. 算法回顾序(学习方法,第一个程序,《程序设计导引及在线实践》习题,移位运算)
  13. android电子时钟码源,android自定义时钟APP源码
  14. JS 原生 锅打灰太狼小游戏
  15. s1 episode1(一)
  16. 从抢票软件到浏览器设计
  17. SqlServer中的Top * With Ties
  18. java poi pdf 导出
  19. python 的statsmodels包,计算Wald tests
  20. 通俗理解TIM定时器并简单使用

热门文章

  1. 如何写出让 CPU 跑得更快的代码?
  2. 有什么事是你当了程序员才知道的? | 每日趣闻
  3. 基于Composer Player 模型加载和相关属性设置
  4. C++中的 Round(),floor(),ceil()
  5. 不可不知的sudo命令
  6. 如何配置一台适用于深度学习的工作站?
  7. PHP图片裁剪_图片缩放_PHP生成缩略图
  8. 简单团队-爬取豆瓣电影TOP250-需求分析
  9. .Net Base64编码
  10. linux --- 进程调度