这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助

前言

我以前很喜欢封装组件,什么东西不喜欢别人的,总喜欢自己搞搞,这让人很有成就感,虽然是重复造轮子,但是能从无聊的crud业务中暂时解脱出来,对我来说也算是一种休息,相信有很多人跟我一样有这个习惯。 这种习惯在独立开发时无所谓,毕竟没人会关心你咋实现的,但是在跟人合作时就给别人造成了很大的困扰了,毕竟每个人封装的东西都是根据自己习惯来的,别人看着多少会有点不顺眼,而且自己封装的组件大概率也是没有写文档和注释的,所以项目其他成员的使用率也不会太高,所以今天,我试着解决这个问题。 另外,我还在一些群里看到有人抱怨vue3不如vue2好用,主要是适应不了setup写法,希望这篇博客能改变你的看法。

怎么用hook改造我的组件

关于hook是什么之类的介绍,我这就不赘述了,请看这篇文章浅谈:为啥vue和react都选择了Hooks?。 前言中说到重复造轮子的组件,除开一些毫无必要的重复以外,有一些功能组件确实需要封装一下,比如说,一些需要请求后端字典到前端展示的下来选择框,点击之后要展示loading状态的按钮,带有查询条件的表单,这些非常常用的业务场景,我们就可以封装成组件,但是封装成组件就会遇到前面说的问题,每个人的使用习惯和封装习惯不一样,很难让每个人都满意,这种场景,就可以让hook来解决。

普通实现

就拿字典选择下拉框来说,如果不做封装,我们是这样写的 (这里拿ant-design-vue组件库来做示例)

<script setup name="DDemo" lang="ts">
import { onMounted, ref } from 'vue'; // 模拟调用接口
function getRemoteData() {
return new Promise<any[]>((resolve) => {
setTimeout(() => {
resolve([
{
key: 1,
name: '苹果',
value: 1,
},
{
key: 2,
name: '香蕉',
value: 2,
},
{
key: 3,
name: '橘子',
value: 3,
},
]);
}, 3000);
});
} const optionsArr = ref<any[]>([]); onMounted(() => {
getRemoteData().then((data) => {
optionsArr.value = data;
});
});
</script> <template>
<div>
<a-select :options="optionsArr" />
</div>
</template> <style lang="less" scoped></style>

看起来很简单是吧,忽略我们模拟调用接口的代码,我们用在ts/js部分的代码才只有6行而已,看起来根本不需要什么封装。

但是这只是一个最简单的逻辑,不考虑接口请求超时和错误的情况,甚至都没考虑下拉框的loading表现。 如果我们把所有的意外情况都考虑到的话,代码就会变得很臃肿了。

<script setup name="DDemo" lang="ts">
import { onMounted, ref } from 'vue'; // 模拟调用接口
function getRemoteData() {
return new Promise<any[]>((resolve, reject) => {
setTimeout(() => {
// 模拟接口调用有概率出错
if (Math.random() > 0.5) {
resolve([
{
key: 1,
name: '苹果',
value: 1,
},
{
key: 2,
name: '香蕉',
value: 2,
},
{
key: 3,
name: '橘子',
value: 3,
},
]);
} else {
reject(new Error('不小心出错了!'));
}
}, 3000);
});
} const optLoading = ref(false);
const optionsArr = ref<any[]>([]); function initSelect() {
optLoading.value = true;
getRemoteData()
.then((data) => {
optionsArr.value = data;
})
.catch((e) => {
// 请求出线错误时将错误信息显示到select中,给用户一个友好的提示
optionsArr.value = [
{
key: -1,
value: -1,
label: e.message,
disabled: true,
},
];
})
.finally(() => {
optLoading.value = false;
});
} onMounted(() => {
initSelect();
});
</script> <template>
<div>
<a-select :loading="optLoading" :options="optionsArr" />
</div>
</template>

这一次,代码直接来到了22行,虽说用户体验确实好了不少,但是这也忒费事了,而且这还只是一个下拉框,页面里有好几个下拉框也是很常见的,如此这般,可能什么逻辑都没写,页面代码就要上百行了。

这个时候,就需要我们来封装一下了,我们有两种选择:

  1. 把字典下拉框封装成一个组件
  2. 把请求、加载中、错误这些处理逻辑封装到hook里;

第一种大家都知道,就不多说了,直接说第二种

封装下拉框hook
import { onMounted, reactive, ref } from 'vue';
// 定义下拉框接收的数据格式
export interface SelectOption {
value: string;
label: string;
disabled?: boolean;
key?: string;
}
// 定义入参格式
interface FetchSelectProps {
apiFun: () => Promise<any[]>;
} export function useFetchSelect(props: FetchSelectProps) {
const { apiFun } = props; const options = ref<SelectOption[]>([]); const loading = ref(false); /* 调用接口请求数据 */
const loadData = () => {
loading.value = true;
options.value = [];
return apiFun().then(
(data) => {
loading.value = false;
options.value = data;
return data;
},
(err) => {
// 未知错误,可能是代码抛出的错误,或是网络错误
loading.value = false;
options.value = [
{
value: '-1',
label: err.message,
disabled: true,
},
];
// 接着抛出错误
return Promise.reject(err);
}
);
}; // onMounted 中调用接口
onMounted(() => {
loadData();
}); return reactive({
options,
loading,
});
}

然后在组件中调用

<script setup name="DDemo" lang="ts">
import { useFetchSelect } from './hook'; // 模拟调用接口
function getRemoteData() {
return new Promise<any[]>((resolve, reject) => {
setTimeout(() => {
// 模拟接口调用有概率出错
if (Math.random() > 0.5) {
resolve([
{
key: 1,
name: '苹果',
value: 1,
},
{
key: 2,
name: '香蕉',
value: 2,
},
{
key: 3,
name: '橘子',
value: 3,
},
]);
} else {
reject(new Error('不小心出错了!'));
}
}, 3000);
});
} // 将之前用的 options,loading,和调用接口的逻辑都抽离到hook中
const selectBind = useFetchSelect({
apiFun: getRemoteData,
});
</script> <template>
<div>
<!-- 将hook返回的接口,通过 v-bind 绑定给组件 -->
<a-select v-bind="selectBind" />
</div>
</template>

这样一来,代码行数直接又从20行降到3行,甚至比刚开始最简单的那个还要少两行,但是功能却一点不少,用户体验也是比较完善的。

如果你觉着上面这个例子不能打动你的话,可以看看下面这个

Loading状态hook

点击按钮,调用接口是另一个我们经常遇到的场景,为了更好的用户体验,提示用户操作已经响应,同时防止用户多次点击,我们要在调用接口的同时将按钮置为loading状态,虽说只有一个loading状态,但是写多了也觉着麻烦。

为此我们可以封装一个非常简单的hook:

hook.ts

import { Ref, ref } from 'vue';

type TApiFun<TData, TParams extends Array<any>> = (...params: TParams) => Promise<TData>;

interface AutoRequestOptions {
// 定义一下初始状态
loading?: boolean;
// 接口调用成功时的回调
onSuccess?: (data: any) => void;
} type AutoRequestResult<TData, TParams extends Array<any>> = [Ref<boolean>, TApiFun<TData, TParams>]; /* 控制loading状态的自动切换hook */
export function useAutoRequest<TData, TParams extends any[] = any[]>(fun: TApiFun<TData, TParams>, options?: AutoRequestOptions): AutoRequestResult<TData, TParams> {
const { loading = false, onSuccess } = options || { loading: false }; const requestLoading = ref(loading); const run: TApiFun<TData, TParams> = (...params) => {
requestLoading.value = true;
return fun(...params)
.then((res) => {
onSuccess && onSuccess(res);
return res;
})
.finally(() => {
requestLoading.value = false;
});
}; return [requestLoading, run];
}

这次把模拟接口的方法单独抽出一个文件

api/index.ts

export function submitApi(text: string) {
return new Promise((resolve, reject) => {
setTimeout(() => {
// 模拟接口调用有概率出错
if (Math.random() > 0.5) {
resolve({
status: "ok",
text: text,
});
} else {
reject(new Error("不小心出错了!"));
}
}, 3000);
});
}

使用:

index.vue

<script setup name="Index" lang="ts">
import { useAutoRequest } from "./hook";
import { Button } from "ant-design-vue";
import { submitApi } from "@/api"; const [loading, submit] = useAutoRequest(submitApi); function onSubmit() {
submit("aaa").then((res) => {
console.log("res", res);
});
}
</script> <template>
<div class="col">
<Button :loading="loading" @click="onSubmit">提交</Button>
</div>
</template>

这样封装一下,我们使用时就不再需要手动切换loading的状态了。

这个hook还有另一种玩法:

hook2.ts

import type { Ref } from "vue";
import { ref } from "vue"; type AutoLoadingResult = [
Ref<boolean>,
<T>(requestPromise: Promise<T>) => Promise<T>
]; /* 在给run方法传入一个promise,会在promise执行前或执行后将loading状态设为true,在执行完成后设为false */
export function useAutoLoading(defaultLoading = false): AutoLoadingResult {
const ld = ref(defaultLoading); function run<T>(requestPromise: Promise<T>): Promise<T> {
ld.value = true;
return requestPromise.finally(() => {
ld.value = false;
});
} return [ld, run];
}

使用:

index.vue

<script setup name="Index" lang="ts">
// import { useAutoRequest } from "./hook";
import { useAutoLoading } from "./hook2";
import { Button } from "ant-design-vue";
import { submitApi, cancelApi } from "@/api"; // const [loading, submit] = useAutoRequest(submitApi); const [commonLoading, fetch] = useAutoLoading(); function onSubmit() {
fetch(submitApi("submit")).then((res) => {
console.log("res", res);
});
} function onCancel() {
fetch(cancelApi("cancel")).then((res) => {
console.log("res", res);
});
}
</script> <template>
<div class="col">
<Button type="primary" :loading="commonLoading" @click="onSubmit">
提交
</Button>
<Button :loading="commonLoading" @click="onCancel">取消</Button>
</div>
</template>

这里也是用到了promise链式调用的特性,在接口调用之后马上将loading置为true,在接口调用完成后置为false。而useAutoRequest则是在接口调用之前就将loading置为true。

useAutoRequest调用时代码更简洁,useAutoLoading的使用则更灵活,可以同时服务给多个接口使用,比较适合提交取消这种互斥的场景。

解放组件

如果你翻看过我的这篇博客一个省心省力的骨架屏实现方案,那么肯定知道在骨架屏组件中,我是用了传入的res对象的code属性来判断当前显示的视图状态。长话短说就是, res是接口返回给前端的数据,如

{
"code":0,
"msg":'查询成功',
"data":{
"username":"小王",
"age":20,
}
}

我们假定当code0时代表成功,不为0表示失败,为-100时表示正在加载,当然接口并不会也不需要返回-100-100是我们本地捏造出来的,只是为了让骨架屏组件显示对应的加载状态。 在页面中使用时,我们需要先声明一个code-100res对象绑定给骨架屏组件,然后在onMounted中调用查询接口,调用成功后更新res对象。

如果像上面这样使用res对象来给骨架屏组件设置状态的话,就感觉非常的麻烦,有时候我们只是要设置一个初始时的加载状态,但是要搞好几行没用的代码,但是如果我们把res拆解成一个个参数单独传递的话,父组件需要维护的变量就会非常多了,这时我们就可以封装hook来解决这个问题,把拆解出来的参数都扔到hook里面保存。

上代码(这部分代码比较长,想要详细了解的话可以去看原文章)

骨架屏组件

SkeletonView/index.vue

<script setup lang="ts">
import { defineProps, computed } from "vue";
import { LoadingOutlined } from "@ant-design/icons-vue";
import { isArray } from "@/utils/is";
import { Button } from "ant-design-vue"; /* status:'loading','error','success','empty' */
type ViewStatus = "loading" | "error" | "success" | "empty"; interface SkeletonProps<T = any> {
status: ViewStatus;
result: T;
placeholderResult: T;
emptyMsg?: string;
errorMsg?: string;
isEmpty?: (result: T) => boolean;
} const props = withDefaults(defineProps<SkeletonProps>(), {
status: "loading",
emptyMsg: "暂无数据",
errorMsg: "未知错误",
}); const emits = defineEmits(["retry"]); const retryClick = () => {
emits("retry");
}; const viewStatus = computed(() => {
const status = props.status; if (status === "success") {
let isEmp = false;
const result = props.result;
if (props.isEmpty) {
isEmp = props.isEmpty(props.result);
} else {
if (isArray(result)) {
isEmp = result.length === 0;
} else if (!result) {
isEmp = true;
} else {
isEmp = false;
}
}
if (isEmp) {
return "empty";
}
return "success";
}
return status;
}); const placeholderData = computed(() => {
if (props.result) {
return props.result;
}
return props.placeholderResult;
});
</script> <template>
<div v-if="viewStatus === 'empty'" key="empty" class="empty_view flex-col">
<span>{{ emptyMsg }}</span>
<Button class="mt4 max-w-160px" @click="retryClick">重试</Button>
</div> <div
key="error"
v-else-if="viewStatus === 'error'"
class="empty_view flex-col"
>
<span>{{ errorMsg }}</span>
<Button class="mt4 max-w-160px" @click="retryClick">重试</Button>
</div> <div
v-else
key="loadingOrContent"
:class="[
placeholderData && viewStatus === 'loading'
? 'skeleton-view-empty-view'
: 'skeleton-view-default-view',
]"
>
<div
v-if="!placeholderData && viewStatus === 'loading'"
class="loading-center"
>
<LoadingOutlined style="font-size: 40px; color: #2a6de5" />
</div>
<slot
v-else
:result="placeholderData"
:status="viewStatus"
:success="viewStatus === 'success'"
:mask="viewStatus === 'loading' ? 'skeleton-mask' : ''"
></slot>
</div>
</template> <style>
.clam-box {
width: 100%;
height: 100%;
}
.empty_view {
padding-top: 50px;
padding-bottom: 50px;
align-items: center;
}
.empty_img {
width: 310px;
height: 218px;
}
.trip_text {
font-size: 20px;
color: #999999;
} .mt4 {
margin-top: 4px;
} .flex-col {
display: flex;
flex-direction: column;
} .loading-center {
padding: 20px;
display: flex;
justify-content: center;
align-items: center;
} .skeleton-view-default-view span,
.skeleton-view-default-view a,
.skeleton-view-default-view img,
.skeleton-view-default-view td,
.skeleton-view-default-view button {
transition-duration: 0.7s;
transition-timing-function: ease;
transition-property: background, width;
} .skeleton-view-empty-view {
position: relative;
pointer-events: none;
} .skeleton-view-empty-view::before {
content: " ";
position: absolute;
width: 100%;
height: 100%;
top: 0;
left: 0;
background: linear-gradient(
110deg,
rgba(255, 255, 255, 0.1) 40%,
rgba(180, 199, 255, 0.3) 50%,
rgba(255, 255, 255, 0.1) 60%
);
background-size: 200% 100%;
background-position-x: 180%;
animation: loading 1s ease-in-out infinite;
z-index: 1;
} @keyframes loading {
to {
background-position-x: -20%;
}
} .skeleton-view-empty-view .skeleton-mask {
position: relative;
}
.skeleton-view-empty-view .skeleton-mask::before {
content: " ";
background-color: #f5f5f5;
position: absolute;
width: 100%;
height: 100%;
border: 1px solid #f5f5f5;
top: -1px;
left: -1px;
z-index: 1;
} .skeleton-view-empty-view button,
.skeleton-view-empty-view span,
.skeleton-view-empty-view input,
.skeleton-view-empty-view td,
.skeleton-view-empty-view a {
color: rgba(0, 0, 0, 0) !important;
border: none;
background: #f5f5f5 !important;
}
/* [src=""],img:not([src])*/
.skeleton-view-empty-view img {
content: url(./no_url.png);
border-radius: 2px;
background: #f5f5f5 !important;
}
</style>

使用 index.vue

<script setup name="SkeletonView" lang="ts">
import SkeletonView from "@/components/SkeletonView/index.vue";
import { useAutoSkeletonView } from "./useAutoSkeletonView";
import { listApi } from "@/api"; const view = useAutoSkeletonView({
apiFun: listApi,
});
</script> <template>
<div class="col">
<SkeletonView
v-slot="{ result }"
v-bind="view.bindProps"
v-on="view.bindEvents"
>
<span>{{ result }}</span>
</SkeletonView>
</div>
</template>

这里的SkeletonView不光用v-bind绑定了hook抛出的属性,还用v-on绑定的事件,目的就是监听请求报错时出现的“重试”按钮的点击事件。

使用优化

经常写react的朋友可能早就看出来了,这不是跟react中的一部分hook用法如出一辙吗?没错,很多人写react就这么写,而且react中绑定hook跟组件更简单,只需要...就可以了,比如:

function Demo(){
const select = useSelect({
apiFun:getDict
})
// 这里可以直接用...将useSelect返回的属性与方法全部绑定给Select组件
return <Select {...select}>;
}

比起vuev-bindv-on算是简便了不少。那么,有没有一种办法也能做到差不多的效果呢?就比如能做到v-xxx="select"

博主首先想到的就是vue的自定义指令了,文档在这里,但是折腾了半天发现行不通,因为自定义指令主要还是针对dom来的。vue官网原话:

总的来说,推荐在组件上使用自定义指令。

那么就只能考虑打包插件了,只要我们在vue解析template之前把v-xxx="select"翻译成v-bind="select.bindProps" v-on="select.bindEvents" 就好了,听起来并不难,只要我们开发的时候规定绑定组件的hook返回格式必须有bindPropsbindEvents就好了。

思路有了,直接开干,现在vue官网的默认创建方式也改成vite,我们就直接写vite的插件(不想看可以跳到最后用现成的):

// component-enhance-hook
import type { PluginOption } from "vite"; // 可以自定义hook绑定的前缀、绑定的属性值合集对应的键和事件合集对应的键
type HookBindPluginOptions = {
prefix?: string;
bindKey?: string;
eventKey?: string;
};
export const viteHookBind = (options?: HookBindPluginOptions): PluginOption => {
const { prefix, bindKey, eventKey } = Object.assign(
{
prefix: "v-ehb",
bindKey: "bindProps",
eventKey: "bindEvents",
},
options
); return {
name: "vite-plugin-vue-component-enhance-hook-bind",
enforce: "pre",
transform: (code, id) => {
const last = id.substring(id.length - 4); if (last === ".vue") {
// 处理之前先判断一下
if (code.indexOf(prefix) === -1) {
return code;
}
// 获取 template 开头
const templateStrStart = code.indexOf("<template>");
// 获取 template 结尾
const templateStrEnd = code.lastIndexOf("</template>"); let templateStr = code.substring(templateStrStart, templateStrEnd + 11); let startIndex;
// 循环转换 template 中的hook绑定指令
while ((startIndex = templateStr.indexOf(prefix)) > -1) {
const endIndex = templateStr.indexOf(`"`, startIndex + 7);
const str = templateStr.substring(startIndex, endIndex + 1);
const obj = str.split(`"`)[1]; const newStr = templateStr.replace(
str,
`v-bind="${obj}.${bindKey}" v-on="${obj}.${eventKey}"`
); templateStr = newStr;
} // 拼接并返回
return (
code.substring(0, templateStrStart) +
templateStr +
code.substring(templateStrEnd + 11)
);
} return code;
},
};
};

应用插件

import { fileURLToPath, URL } from "node:url";

import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
import vueJsx from "@vitejs/plugin-vue-jsx"; import { viteHookBind } from "./vBindPlugin"; // https://vitejs.dev/config/
export default defineConfig({
plugins: [vue(), vueJsx(), viteHookBind()],
resolve: {
alias: {
"@": fileURLToPath(new URL("./src", import.meta.url)),
},
},
});

修改一下vue中的用法

<script setup name="SkeletonView" lang="ts">
import SkeletonView from "@/components/SkeletonView/index.vue";
import { useAutoSkeletonView } from "./useAutoSkeletonView";
import { listApi } from "@/api"; const view = useAutoSkeletonView({
queryInMount: true,
apiFun: listApi,
placeholderResult: [
{
key: 1,
name: "苹果",
value: 1,
},
{
key: 2,
name: "香蕉",
value: 2,
},
{
key: 3,
name: "橘子",
value: 3,
},
],
});
</script> <template>
<div class="col">
<SkeletonView v-slot="{ result }" v-ehb="view">
<span>{{ result }}</span>
</SkeletonView>
</div>
</template>

OK! 完成了!

使用npm安装

不过我也提前打包编译好了发布在了npm上,需要的话可以直接使用这个

npm i vite-plugin-vue-hook-enhance -D

改一下引入方式就可以了

import { viteHookBind } from "vite-plugin-vue-hook-enhance";

本文转载于:

https://juejin.cn/post/7181712900094951483

如果对您有所帮助,欢迎您点个关注,我会定时更新技术文档,大家一起讨论学习,一起进步。

记录--【vue3】写hook三天,治好了我的组件封装强迫症。的更多相关文章

  1. 也用 Log4Net 之将自定义属性记录到文件中 (三)

    也用 Log4Net  之将自定义属性记录到文件中 (三)  即解决了将自定义属性记录到数据库之后.一个新的想法冒了出来,自定义属性同样也能记录到文件中吗?答案是肯定的,因为Log4Net既然已经考虑 ...

  2. 假设高度已知,请写出三栏布局,其中左栏、右栏各为300px,中间自适应的五种方法

    假设高度已知,请写出三栏布局,其中左栏.右栏各为300px,中间自适应的五种方法 HTML CSS 页面布局 题目:假设高度已知,请写出三栏布局,其中左栏.右栏各为300px,中间自适应 <!D ...

  3. Log4Net 之将自定义属性记录到文件中 (三)

    原文:Log4Net 之将自定义属性记录到文件中 (三) 即解决了将自定义属性记录到数据库之后.一个新的想法冒了出来,自定义属性同样也能记录到文件中吗?答案是肯定的,因为Log4Net既然已经考虑到了 ...

  4. 前端一面/面试常考题1-页面布局:假设高度已知,请写出三栏布局,其中左栏、右栏宽度各为300px,中间自适应。

    题目:假设高度已知,请写出三栏布局,其中左栏.右栏宽度各为300px,中间自适应. [题外话:日常宣读我的目标===想要成为一名优雅的程序媛] 一.分析 1. 题目真的像我们想得这么简单吗? 其实不然 ...

  5. upload 上传 加token 在 :headers='headers' 注意 不要直接写$refs.upload.headers = {} 这样vue会警告 修改组件内部变量

    upload 上传 加token 在 :headers='headers' 注意 不要直接写$refs.upload.headers = {} 这样vue会警告 修改组件内部变量 <Upload ...

  6. 记录一下我的三天清明节假期,TP5.1写企业站

    在假期前就计划利用这三天时间写一个企业站,包括pc和wap,和微信公众平台 在计划时有些功能没有想好,导致后面踩了不少坑,前期计划一定要尽量做详细,表字段设计也要考虑好,不然后期开始写代码时会需要来回 ...

  7. 基于 vite2 + Vue3 写一个在线帮助文档工具

    提起帮助文档,想必大家都会想到 VuePress等,我也体验了一下,但是感觉和我的思路不太一样,我希望的是那种可以直接在线编辑文档,然后无需编译就可以直接发布的方式,另外可以在线写(修改)代码并且运行 ...

  8. <记录学习>(前三天)京东页面各种注意点

    培训学校第1到3天先学习HTML现在流行的是HTML5,目前学习的是HTML5规范.(给有基础一定的人学习)前三天学习的是京东页面的编写,和以前写的不同,页面看上去和自己写的一样,但老师讲的还是有很多 ...

  9. usb输入子系统写程序(三)

    目录 usb输入子系统写程序 小结 内核修改 怎么写代码 类型匹配 probe disconnect 程序设计 1th匹配probe 2th 获取usb数据 3th 输入子系统上报按键 title: ...

  10. 跟我一起写Makefile(三)

    书写规则———— 规则包含两个部分,一个是依赖关系,一个是生成目标的方法. 在Makefile中,规则的顺序是很重要的,因为,Makefile中只应该有一个最终目标,其它的目标都是被这个目标所连带出来 ...

随机推荐

  1. Series基础

    目录 创建Series对象 1) 创建一个空Series对象 2) ndarray创建Series对象 3) dict创建Series对象 4) 标量创建Series对象 访问Series数据 1) ...

  2. NC22544 车站

    题目链接 题目 题目描述 一个国家有n个城市,有n-1条道路连接,保证联通.还有m条铁路,从1~m编号,第i条铁路是从ui到vi的简单路径,多次询问一段区间的铁路的车站. 一个点可以作为区间[L,R] ...

  3. NC24734 [USACO 2010 Mar G]Great Cow Gathering

    题目链接 题目 题目描述 Bessie is planning the annual Great Cow Gathering for cows all across the country and, ...

  4. NVM Feature— Reservation(NVME 学习笔记五)

    8.8 Reservations 预订 NVMe的reservation预订功能,用于让两个或多个主机能够协调配合的访问共享namespace.使用这些功能的协议和方式超出了本规格说明书的范围.对这些 ...

  5. OAuth2 Authorization Server

    基于Spring Security 5 的 Authorization Server的写法 先看演示 pom.xml <?xml version="1.0" encoding ...

  6. 正则表达式(Regular Expression)详解

    1 前言 正则表达式主要用于复杂文本处理,如模式匹配.格式检验.文本替换等.常用的通配符有: ^, $, *, ., , -, +, ?, &, |, (), [], {} 2 String中 ...

  7. Java并发编程实例--1.创建和运行一个线程

    从这一篇开始写Java并发编程实例,内容都翻译整理自书籍:<Java 7 Concurrency Cookbook> 谈到线程,无法逃避的一个问题就是: 并发(concurrency)和并 ...

  8. sort.interface接口

    一个内置的排序算法需要知道三个东西:序列的长度,表示两个元素比较的结果,一种交换两个元素的方式:这就是sort.Interface的三个方法: package sort type Interface ...

  9. 数据抽取平台pydatax介绍--实现和项目使用

    数据抽取平台pydatax实现过程中,有2个关键点: 1.是否能在python3中调用执行datax任务,自己测试了一下可以,代码如下:    这个str1就是配置的shell文件 try: resu ...

  10. DataGear 数据可视化看板整合前端框架Vue

    DataGear 看板JS对象的loadUnsolvedCharts()函数,用于异步加载页面端动态生成的图表元素,利用它,可以很方便整合Angular.React.Vue等前端框架. 本文以Vue为 ...