记录--【vue3】写hook三天,治好了我的组件封装强迫症。
这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助
前言
我以前很喜欢封装组件,什么东西不喜欢别人的,总喜欢自己搞搞,这让人很有成就感,虽然是重复造轮子,但是能从无聊的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
行,虽说用户体验确实好了不少,但是这也忒费事了,而且这还只是一个下拉框,页面里有好几个下拉框也是很常见的,如此这般,可能什么逻辑都没写,页面代码就要上百行了。
这个时候,就需要我们来封装一下了,我们有两种选择:
- 把字典下拉框封装成一个
组件
; - 把请求、加载中、错误这些处理逻辑封装到
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,
}
}
我们假定当code
为0
时代表成功,不为0
表示失败,为-100
时表示正在加载,当然接口并不会也不需要返回-100
,-100
是我们本地捏造出来的,只是为了让骨架屏组件显示对应的加载状态。 在页面中使用时,我们需要先声明一个code
为-100
的res
对象绑定给骨架屏组件,然后在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}>;
}
比起vue
的v-bind
和v-on
算是简便了不少。那么,有没有一种办法也能做到差不多的效果呢?就比如能做到v-xxx="select"
。
博主首先想到的就是vue的自定义指令了,文档在这里,但是折腾了半天发现行不通,因为自定义指令主要还是针对dom来的。vue官网原话:
总的来说,不推荐在组件上使用自定义指令。
那么就只能考虑打包插件了,只要我们在vue
解析template
之前把v-xxx="select"
翻译成v-bind="select.bindProps" v-on="select.bindEvents"
就好了,听起来并不难,只要我们开发的时候规定绑定组件的hook返回格式必须有bindProps
和bindEvents
就好了。
思路有了,直接开干,现在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三天,治好了我的组件封装强迫症。的更多相关文章
- 也用 Log4Net 之将自定义属性记录到文件中 (三)
也用 Log4Net 之将自定义属性记录到文件中 (三) 即解决了将自定义属性记录到数据库之后.一个新的想法冒了出来,自定义属性同样也能记录到文件中吗?答案是肯定的,因为Log4Net既然已经考虑 ...
- 假设高度已知,请写出三栏布局,其中左栏、右栏各为300px,中间自适应的五种方法
假设高度已知,请写出三栏布局,其中左栏.右栏各为300px,中间自适应的五种方法 HTML CSS 页面布局 题目:假设高度已知,请写出三栏布局,其中左栏.右栏各为300px,中间自适应 <!D ...
- Log4Net 之将自定义属性记录到文件中 (三)
原文:Log4Net 之将自定义属性记录到文件中 (三) 即解决了将自定义属性记录到数据库之后.一个新的想法冒了出来,自定义属性同样也能记录到文件中吗?答案是肯定的,因为Log4Net既然已经考虑到了 ...
- 前端一面/面试常考题1-页面布局:假设高度已知,请写出三栏布局,其中左栏、右栏宽度各为300px,中间自适应。
题目:假设高度已知,请写出三栏布局,其中左栏.右栏宽度各为300px,中间自适应. [题外话:日常宣读我的目标===想要成为一名优雅的程序媛] 一.分析 1. 题目真的像我们想得这么简单吗? 其实不然 ...
- upload 上传 加token 在 :headers='headers' 注意 不要直接写$refs.upload.headers = {} 这样vue会警告 修改组件内部变量
upload 上传 加token 在 :headers='headers' 注意 不要直接写$refs.upload.headers = {} 这样vue会警告 修改组件内部变量 <Upload ...
- 记录一下我的三天清明节假期,TP5.1写企业站
在假期前就计划利用这三天时间写一个企业站,包括pc和wap,和微信公众平台 在计划时有些功能没有想好,导致后面踩了不少坑,前期计划一定要尽量做详细,表字段设计也要考虑好,不然后期开始写代码时会需要来回 ...
- 基于 vite2 + Vue3 写一个在线帮助文档工具
提起帮助文档,想必大家都会想到 VuePress等,我也体验了一下,但是感觉和我的思路不太一样,我希望的是那种可以直接在线编辑文档,然后无需编译就可以直接发布的方式,另外可以在线写(修改)代码并且运行 ...
- <记录学习>(前三天)京东页面各种注意点
培训学校第1到3天先学习HTML现在流行的是HTML5,目前学习的是HTML5规范.(给有基础一定的人学习)前三天学习的是京东页面的编写,和以前写的不同,页面看上去和自己写的一样,但老师讲的还是有很多 ...
- usb输入子系统写程序(三)
目录 usb输入子系统写程序 小结 内核修改 怎么写代码 类型匹配 probe disconnect 程序设计 1th匹配probe 2th 获取usb数据 3th 输入子系统上报按键 title: ...
- 跟我一起写Makefile(三)
书写规则———— 规则包含两个部分,一个是依赖关系,一个是生成目标的方法. 在Makefile中,规则的顺序是很重要的,因为,Makefile中只应该有一个最终目标,其它的目标都是被这个目标所连带出来 ...
随机推荐
- FreeSWITCH添加g729编码及pcap音频提取
操作系统 : debian 11 (bullseye,docker).Windows10_x64 FreeSWITCH版本 :1.10.9 Docker版本:23.0.6 Python 版本 : ...
- NC16810 [NOIP1999]拦截导弹
题目链接 题目 题目描述 某国为了防御敌国的导弹袭击,发展出一种导弹拦截系统.但是这种导弹拦截系统有一个缺陷:虽然它的第一发炮弹能够到达任意的高度,但是以后每一发炮弹都不能高于前一发的高度.某天,雷达 ...
- Ubuntu18.04 Server部署Flannel网络的Kubernetes
准备服务器 ESXi6.5安装Ubuntu18.04 Server, 使用三台主机, 计划使用hostname为 kube01, kube02, kube03, 配置为2核4G/160G, K8s要求 ...
- Java8函数式接口Predicate实战
关于函数式接口 函数式接口 Funcational Interface 是指接口范围内只允许有一个抽象方法(不包括default和static方法)的接口.Java中有一些预定义的函数接口,如Pred ...
- spring boot读取json文件并实现接口查询
0.源码下载 https://download.csdn.net/download/IndexMan/84238085 1.说明 最近需要在spring boot项目启动时读取json文件并保存到Li ...
- 识别主机名和IP地址
文章来源:https://oracle-base.com/articles/misc/identifying-host-names-and-addresses Identifying Host Nam ...
- 逆向实战31——xhs—xs算法分析
前言 本文章中所有内容仅供学习交流,抓包内容.敏感网址.数据接口均已做脱敏处理,严禁用于商业用途和非法用途,否则由此产生的一切后果均与作者无关,若有侵权,请联系我立即删除! 公众号链接 目标网站 aH ...
- 【MongoDB】C# .Net MongoDB常用语法
1.1.驱动安装 使用NuGet包管理器安装MongoDB C#驱动:MongoDB.Driver 1.2. C#连接MongoDB //获取MongoDB连接客户端 MongoClient clie ...
- Java 通过属性名称读取或者设置实体的属性值
原因 项目实战中有这个需求,数据库中配置对应的实体和属性名称,在代码中通过属性名称获取实体的对应的属性值. 解决方案 工具类,下面这个工具是辅助获取属性值 import com.alibaba.fas ...
- 在MATPool矩池云完成Pytorch训练MNIST数据集
本文为矩池云入门手册的补充:Pytorch训练MNIST数据集代码运行过程. 案例代码和对应数据集,以及在矩池云上的详细操作可以在矩池云入门手册中查看,本文基于矩池云入门手册,默认用户已经完成了机器租 ...