antd 3.x升4.x踩坑之路~
我们是袋鼠云数栈 UED 团队,致力于打造优秀的一站式数据中台产品。我们始终保持工匠精神,探索前端道路,为社区积累并传播经验价值。
兼容性问题
第三方依赖兼容问题
- React - 最低 v16.9,部分组件使用 hooks 重构 react升级相关文档
- Less - 最低 v3.1.0,建议升级到 less 4.x
- @ant-design/icons-antd - 不再内置 Icon 组件,请使用独立的包
对 3.x 的兼容性处理
或许是考虑到部分组件升级的毁坏性,antd4.x 中依然保留了对 3.x 版本的兼容,废弃的组件通过 @ant-design/compatible 保持兼容,例如 Icon, Form
注:建议 @ant-design/compatible 仅在升级过程中稍作依赖,升级 4.x 请完全剔除对该过渡包的依赖
升级步骤(只有一步)
1、@ant-design/codemod-v4 自带升级脚本,会自动替换代码
# 通过 npx 直接运行
npx -p @ant-design/codemod-v4 antd4-codemod apps/xxxx
# 或者全局安装
# 使用 npm
npm i -g @ant-design/codemod-v4
# 或者使用 yarn
yarn global add @ant-design/codemod-v4
# 运行
antd4-codemod src
注意: 该命令和脚本只会进行代码替换,不会进行AntD的版本升级,需要手动将其升级至4.22.5
该命令完成的工作:
1. 将 Form 与 Mention 组件通过 @ant-design/compatible 包引入
2. 用新的 @ant-design/icons 替换字符串类型的 icon 属性值
3. 将 Icon 组件 + type =“” 通过 @ant-design/icons 引入
4. 将 v3 LocaleProvider 组件转换成 v4 ConfigProvider 组件
5. 将 Modal.method() 中字符串 icon 属性的调用转换成从 @ant-design/icons 中引入
antd4-codemod
上图这类报错是 Icon 组件自动替换错误,有 2 种处理方式:
报错文件的 Icon 比较少的情况,可以直接手动替换该文件中的 Icon 组件。具体替换成 Icon 中的哪个组件可以根据 type 在 Icon文档 中找。
下图中是具体报错的节点,可以看到 JSXSpreadAttribute 节点也就是拓展运算符中没有 name 属性,所以把 Icon 组件的拓展运算符改一下再执行替换脚本就可以了
antd4 问题修复
styled-components
styled-components 依赖需要转换写法
Icon
不要使用兼容包的 icon
在 3.x 版本中,Icon 会全量引入所有 svg 图标文件,增加了打包产物
在 4.x 版本中,对 Icon 进行了按需加载,将每个 svg 封装成一个组件
注:antd 不再内置 Icon 组件,请使用独立的包 @ant-design/icons
使用
import { Icon } from 'antd';
mport { SmileOutlined } from '@ant-design/icons'; const Demo = () => (
<div>
<Icon type="smile" />
<SmileOutlined />
<Button icon={<SmileOutlined />} />
</div>
);
兼容
import { Icon } from '@ant-design/compatible';
const Demo = () => (
<div>
<Icon type="smile" />
<Button icon="smile" />
</div>
);
Form
Form.create()
在 3.x 中,表单中任意一项的修改,都会导致 Form.create() 包裹的表单重新渲染,造成性能消耗
在 4.x 中,Form.create() 不再使用
如果需要使用 form 的 api,例如 setFieldsValue 等,需要通过 Form.useForm()
创建 Form 实体进行操作
- 函数组件写法
// antd v4
const Demo = () => {
const [form] = Form.useForm(); React.useEffect(() => {
form.setFieldsValue({
username: 'Bamboo',
});
}, []); return (
<Form form={form} {...props}> ... </Form>
)
};
- 如果是 class component, 也可以通过 ref 获取
class Demo extends React.Component {
formRef = React.createRef(); componentDidMount() {
this.formRef.current.setFieldsValue({
username: 'Bamboo',
});
} render() {
return (
<Form ref={this.formRef}>
<Form.Item name="username" rules={[{ required: true }]}>
<Input />
</Form.Item>
</Form>
);
}
}
当我们使用 From.create() 的时候,可能会传入参数,做数据处理,例如:
export const FilterForm: any = Form.create<Props>({
onValuesChange: (props, changedValues, allValues) => {
const { onChange } = props;
onChange(allValues);
},
})(Filter);
由于 Form.create 的删除,需要放到 <Form>
中
<Form
ref={this.formRef}
layout="vertical"
className="meta_form"
onValuesChange={(_, allValues) => {
const { onChange } = this.props;
onChange(allValues);
}}
>
getFieldDecorator
在 4.x 中,不在需要 getFieldDecorator 对 Item 进行包裹。
注意以下问题
- 将之前写在 getFieldDecorator 中的 name, rules 等移到属性中
- 初始化在 form 中处理,避免同名字段冲突问题
- 关于表单联动的问题,官方提供了 shouldUpdate 方法,
// antd v4
const Demo = () => (
<Form initialValues={{ username: 'yuwan' }}>
<Form.Item name="username" rules={[{ required: true }]}>
<Input />
</Form.Item>
</Form>
);
initialValue
历史问题
initialValue 从字面意来看,就是初始值 defaultValue,但是可能会有部分同学使用他的时候会误以为 initialValue 等同于 value
造成这样的误解是因为在 3.x 的版本中,一直存在一个很神奇的问题,受控组件的值会跟随 initialValue 改变
看下面的例子,点击 button 修改 username, input 框的 value 也会随之改变
const Demo = ({ form: { getFieldDecorator } }) => (
const [username, setUsername] = useState('');
const handleValueChange = () => {
setUsername('yuwan');
}
return (
<Fragment>
<Form>
<Form.Item>
{getFieldDecorator('username', {
initialValue: username,
rules: [{ required: true }],
})(<Input />)}
</Form.Item>
</Form>
<Button onClick={handleValueChange}>Change</Button>
</Fragment>
)
);
const WrappedDemo = Form.create()(Demo);
但当 input 框被编辑过,initialValue 和 input 的绑定效果就消失了,正确的做法应该是通过 setFieldsVlaue 方法去 set 值
4.x 版本的 initialValue
在 4.x,antd 团队已经把这个 bug 给解了,并且其一是为了 name 重名问题,二是再次强调其初始值的功能,现在提到 Form 中了,
当然,如果继续写在 Form. Item 中也是可以的,但需要注意优先级
shouldUpdate
前面有说过,form 表单不再会因为表单内部某个值的改变而重新渲染整个结构,而设有 shouldUpdate 为 true 的 Item,任意变化都会使该 Form. Item 重新渲染
它会接收 render props,从而允许你对此进行控制
这里稍微注意一下,请勿在设置 shouldUpdate 的外层 Form. Item 上添加 name, 否则,你会得到一个 error
<Form.Item shouldUpdate={(prev, next) => prev.name !== next.name}>
{form => form.getFieldValue('name') === 'antd' && (
<Form.Item name="version">
<Input />
</Form.Item>
)}
</Form.Item>
在使用 shouldUpdate 的时候,需要在第一个 Form.Item 上加上 noStyle,否则就会出现下面的情况,会有留白占位的情况
validateTrigger
onBlur
时不再修改选中值,且返回 React 原生的 event
对象。
如果你在使用兼容包的 Form 且配置了 validateTrigger
为 onBlur
,请改至 onChange
以做兼容。
validator
在 antd3 时,我们使用 callback 返回报错。但是 antd4 对此做了修改,自定义校验,接收 Promise 作为返回值。示例参考
- antd3 的写法
<FormItem label="具体时间" {...formItemLayout}>
{getFieldDecorator('specificTime', {
rules: [
{
required: true,
validator: (_, value, callback) => {
if (!value || !value.hour || !value.min) {
return callback('具体时间不可为空');
}
callback();
},
},
],
})(<SpecificTime />)}
</FormItem>
- antd4 的写法
<FormItem
label="具体时间"
{...formItemLayout}
name="specificTime"
rules={[
{
required: true,
validator: (_, value) => {
if (!value || !value.hour || !value.min) {
return Promise.reject('具体时间不可为空');
}
return Promise.resolve();
},
},
]}
>
<SpecificTime />)
</FormItem>
validateFields
不在支持 callback,该方法会直接返回一个 Promise,可以通过 then / catch 处理
this.formRef.validateFields()
.then((values) => {
onOk({ ...values, id: appInfo.id || '' });
})
.catch(({ errorFields }) {
this.formRef.scrollToField(errorFields[0].name);
})
或者使用 async/await
try {
const values = await validateFields();
} catch ({ errorFields }) {
scrollToField(errorFields[0].name);
}
validateFieldsAndScroll
该 api 被拆分了,将其拆分为更为独立的 scrollToField
方法
onFinishFailed = ({ errorFields }) => {
form.scrollToField(errorFields[0].name);
};
form.name
在 antd 3.x 版本,绑定字段时,可以采用.
分割的方式。如:
getFieldDecorator('sideTableParam.primaryKey')
getFieldDecorator('sideTableParam.primaryValue')
getFieldDecorator('sideTableParam.primaryName')
在最终获取 values 时,antd 3.x 的版本会对字段进行汇总,得到如下:
const values = {
sideTableParam: {
primaryKey: xxx,
primaryValue: xxx,
primaryName: xxx,
}
}
而在 antd 4.x下,会得到如下的values 结果:
const values = {
'sideTableParam.primaryKey': xxx,
'sideTableParam.primaryValue': xxx,
'sideTableParam.primaryName': xxx,
}
解决方法:
在 antd 4.x 版本传入数组
name={['sideTableParam', 'primaryKey']}
name={['sideTableParam', 'primaryValue']}
name={['sideTableParam', 'primaryName']}
使用 setFieldsValue 设置值:
setFieldsValue({
sideTableParam: [
{
primaryKey: 'xxx',
primaryValue: 'xxx',
primaryName: 'xxx',
},
],
});
当我们使用 name={['sideTableParam', 'primaryKey']} 方式绑定值的时候,与其关联的 dependencies/getFieldValue 都需要设置为['sideTableParam', 'primaryKey']
例如:
<FormItem dependencies={[['alert', 'sendTypeList']]} noStyle>
{({ getFieldValue }) => {
const isShowWebHook = getFieldValue(['alert', 'sendTypeList'])?.includes(
ALARM_TYPE.DING
);
return (
isShowWebHook &&
RenderFormItem({
item: {
label: 'WebHook',
key: ['alert', 'dingWebhook'],
component: <Input placeholder="请输入WebHook地址" />,
rules: [
{
required: true,
message: 'WebHook地址为必填项',
},
],
initialValue: taskInfo?.alert?.dingWebhook || '',
},
})
);
}}
</FormItem>
当我们希望通过 validateFields 拿到的数据是数组时,例如这样:
我们可以设置为这样
const formItems = keys.map((k: React.Key) => (
<Form.Item key={k} required label="名称">
<Form.Item
noStyle
name={['names', k]}
rules={[
{ required: true, message: '请输入标签名称' },
{ validator: utils.validateInputText(2, 20) },
]}
>
<Input placeholder="请输入标签名称" style={{ width: '90%', marginRight: 8 }} />
</Form.Item>
<i className="iconfont iconicon_deletecata" onClick={() => this.removeNewTag(k)} />
</Form.Item>
));
Tooltip
extra
<FormItem
label="过滤条件"
extra={
<Tooltip title={customSystemParams}>
系统参数配置
<QuestionCircleOutlined />
</Tooltip>
}
>
<Input.TextArea />
</FormItem>
Select
rc-select
底层重写
- 解决些许历史问题
- rc-select & rc-select-tree 的 inputValue & searchValue 之争
rc-select-tree 是 rc-select 结合 tree 写的一个组件,相似但又不同,searchValue 就是其中一点,也不是没人提过 issue,只是人的忘性很大,时间长了就忘了,混了,导致在 rc-select 中甚至出现了 searchValue 的字样 - inputValue 历史问题,this.state.inputValue
也不是不想改,只是改了之后改出了一堆 bug,真真应了一句话,每一个历史包袱的存在,都有他存在的原因,, - onSelect 清空了值,又会被 onChange 赋值回来
- rc-select & rc-select-tree 的 inputValue & searchValue 之争
- 模块复用
在新版的 rc-select
中,antd 官方抽取了一个 generator 方法。它主要接收一个 OptionList
的自定义组件用于渲染下拉框部分。这样我们就可以直接复用选择框部分的代码,而自定义 Select 和 TreeSelect 对应的列表或者树形结构了。
labelInValue
在 3.x 版本为
在 4.x 版本为
Table
fixed
固定列时,文字过长导致错位的问题,被完美解决了,✿✿ヽ(°▽°)ノ✿
历史原因
3.x 中对 table fixed 的实现,是写了两个 table, 顶层 fixed 的是一个,底层滚动的是一个,这样,出现这种错位的问题就很好理解了。
要解决也不是没有办法,可以再特定的节点去测算表格列的高度,但是这个行为会导致重排,会影响性能问题
解决方案
4.x 中,table fixed 不在通过两个 table 来实现,他使用了一个 position 的新特性:position: sticky;
元素根据正常文档流进行定位,然后相对它的_最近滚动祖先(nearest scrolling ancestor)_和 containing block (最近块级祖先 nearest block-level ancestor),包括 table-related 元素,基于
top
,right
,bottom
, 和left
的值进行偏移。偏移值不会影响任何其他元素的位置。
优点
- 根据正常文档流进行定位
- 相对最近滚动祖先 & 最近块级祖先进行偏移
缺点
- 不兼容 <= IE11
解决了使用 absolute | fixed 脱离文档流无法撑开高度的问题,也不在需要对高度进行测量
table.checkbox
问题描述
资产升级后,checkbox 宽度被挤压了。
解决方案
通过在 rowSelection 中设置 columnWidth 和 fixed 解决。
const rowSelection = {
fixed: true,
columnWidth: 45,
selectedRowKeys,
onChange: this.onSelectChange,
};
渲染条件
antd4 Table 对渲染条件进行了优化,对 props 进行“浅比较”,如果没有变化不会触发 render。
类名更改
. ant-table-content 更改为 .ant-table-container
.ant-form-explain 更改为 .ant-form-item-explain
dataIndex 修改
在 antd3.0 的时候,我们采用 user.userName 能够读到嵌套的属性
{
title: '账号',
dataIndex: 'user.userName',
key: 'userName',
width: 200,
}
antd4.0 对此做了修改,同 Form 的 name
{
title: '账号',
dataIndex: ['user', 'userName'],
key: 'userName',
width: 200,
}
table pagination showSizeChanger
问题描述
升级 antd4 后,发现一些表格分页器多了 pageSize 切换的功能,代码中 onChange 又未对 size 做处理,会导致 底部分页器 pageSize 和数据对不上,因此需要各自排查 Table 的 pagination 和 Pagination 组件,和请求列表接口的参数
<Table
rowKey="userId"
pagination={{
total: users.totalCount,
defaultPageSize: 10,
}}
onChange={this.handleTableChange}
style={{ height: tableScrollHeight }}
loading={this.state.loading}
columns={this.initColumns()}
dataSource={users.data}
scroll={{ x: 1100, y: tableScrollHeight }}
/>
handleTableChange = (pagination: any) => {
this.setState(
{
current: pagination.current,
},
this.search
);
};
search = (projectId?: any) => {
const { name, current } = this.state;
const { project } = this.props;
const params: any = {
projectId: projectId || project.id,
pageSize: 10,
currentPage: current || 1,
name: name || undefined,
removeAdmin: true,
};
this.loadUsers(params);
};
antd4.0 对此做了修改,同 Form 的 name
<Table
rowKey="userId"
pagination={{
showTotal: (total) => `共${total}条`,
total: users.totalCount,
current,
pageSize,
}}
onChange={this.handleTableChange}
style={{ height: tableScrollHeight }}
loading={this.state.loading}
columns={this.initColumns()}
dataSource={users.data}
scroll={{ x: 1100, y: tableScrollHeight }}
/>
handleTableChange = (pagination: any) => {
this.setState(
{
current: pagination.current,
pageSize: pagination.pageSize,
},
this.search
);
};
search = (projectId?: any) => {
const { name, current, pageSize } = this.state;
const { project } = this.props;
const params: any = {
projectId: projectId || project.id,
pageSize,
currentPage: current || 1,
name: name || undefined,
removeAdmin: true,
};
this.loadUsers(params);
};
另外,一些同学在 Table 中 既写了 onChange,也写了 onShowSizeChange,这个时候要注意,当切换页码条数的时候两个方法都会触发,onShowSizeChange 先触发,onChange 后触发,这个时候如果 onChange 内未对 pageSize 做处理可能导致切页失败,看下面代码就明白了,写的时候稍微注意一下即可。
table sorter columnKey
问题描述
表格中如果要对表格某一字段进行排序需要在 columns item 里设置 sorter 字段,然后在 onChange 里拿到 sorter 对象进行参数处理,再请求数据,需要注意的是,很多用到了 sorter.columnKey 来进行判断,容易出现问题,sorter.columnKey === columns item.key,如果未设置 key,那么获取到的 columnKey 就为空,导致搜索失效,要么设置 key,再进行获取,同理, sorter.field === columns item.dataIndex,设置 dataIndex,通过 sorter.field 进行获取,两者都可以
columns={
[
{
title: '创建时间',
dataIndex: 'gmtCreate1',
key: 'aa',
sorter: true,
render(n: any, record: any) {
return DateTime.formatDateTime(record.gmtCreate);
}
},
...
]
}
onChange={(pagination: any, filters: any, sorter: any) {
console.log(pagination, '--pagination');
console.log(filters, '--filters');
console.log(sorter, '--sorter');
}}
Tree
Tree 组件取消 value 属性,现在只需要添加 key 属性即可
特别注意, 此问题会导致功能出问题,需要重点关注!!!
在项目中经常在 TreeItem 中增加参数,如:<TreeItem value={value} data={data} >
。在拖拽等回调中就可以通过 nodeData.props.data
的方式获取到 data 的值。
但在 antd4 中,获取参数的数据结构发生了改变,原先直接通过 props 点出来的不行了。
有两种方式取值
- 不使用props。直接采用 nodeData.data 的方式,也可以直接拿到
- 继续使用 props。在antd4中,还是可以通过 props 找到参数,只不过 antd 会把所有参数使用 data 进行包裹。就需要改成
nodeData.props.data.data
新版数据结构如下:
drag
拖拽节点位置的确定与 3.x 相比进行了变更,官网并没有说明。具体如下图
左侧为 3.x,右侧为 4.x。
在3.x版本,只要把节点拖拽成目标节点的上中下,即代表着目标节点的同级上方,子集,同级下方
在 4.x 版本,是根据当前拖拽节点与目标节点的相对位置进行确定最终的拖拽结果。
当拖拽节点处于目标节点的下方,且相对左侧对齐的位置趋近于零,则最终的位置为目标节点的同级下方。
当拖拽节点处于目标节点的下方,且相对左侧一个缩近的位置。则最终的位置为目标节点的子集。
当拖拽节点处于目标节点的上方,且相对左侧对齐的位置趋近于零,则最终的位置为目标节点的同级上方。
Pagination
Pagination
自 4.1.0 版本起,会默认将 showSizeChanger
参数设置为 true ,因而在数据条数超过50时,pageSize 切换器会默认显示。这个变化同样适用于 Table 组件。可通过 showSizeChanger: false
关闭
如果 size 属性值为 small,则删除 size 属性。
Drawer
当我们在 Drawer 上 设置了 getContainer={false} 属性之后,Drawer 会添加上 .ant-drawer-inline 的类名导致我们 position: fixed 失效
Button
在 antd 3.0 中危险按钮采用 type
使用如下:
设计改动点 type、dangr 属性
Tabs
使标签页不被选中
// 3.x
activeKey={undefined}
// 4.x
activeKey={null}
总结
该篇文章详细讲解了如何从 antd3 升级到 antd4 其中的步骤,以及团队在实践过程中发现的一些问题和对应的解决方案。
antd 3.x升4.x踩坑之路~的更多相关文章
- html2canvas的踩坑之路
html2canvas的踩坑之路 前言 早有耳闻这个html2canvas比较坑,但无奈于产品需求的压迫,必须实现html转图片的功能,自此走上了填坑之路,好在最后的效果还算令人满意,这才没有误了产品 ...
- MySQL Connector/NET 使用小结(踩坑之路)
背景描述 根据项目的需要,需连接MySQL获取数据. 首先,先了解一下项目的情况: 之前的代码是C#编写的的, 运行时:.NETFramework3.5. 由于项目已经部署上线,因此不能升级运行时,这 ...
- Android 上传开源项目到 jcenter 实战踩坑之路
本文微信公众号「AndroidTraveler」首发. 背景 其实 Android 上传开源项目到 jcenter 并不是一件新鲜事,网上也有很多文章. 包括我本人在将开源项目上传到 jcenter ...
- Android SDK 开发——发布使用踩坑之路
前言 在 Android 开发过程中,有些功能是通用的,或者是多个业务方都需要使用的. 为了统一功能逻辑及避免重复开发,因此将该功能开发成一个 SDK 是相当有必要的. 背景 刚好最近自己遇到了类似需 ...
- jQuery升级踩坑之路
1.使用了被废弃的jQuery.browser属性 jQuery 从 1.9 版开始,移除了 $.browser 和 $.browser.version , 取而代之的是 $.support . 在更 ...
- Java踩坑之路
陆陆续续学Java也快一年多了,从开始的一窍不通到现在的初窥门径,我努力过,迷茫过,痛过,乐过,反思过,沉淀过.趁着新年,我希望能把这些东西记下来,就当是我一路走来的脚印. 一.初识网站应用 记得第一 ...
- Mahout踩坑之路
一.版本对比 公司版Mahout 由于Mahout只能允许于hadoop0.20以上版本上,而百度的hadoop是hadoop0.19的一个分支.因此百度HPC组曾经将Mahout移植到百度的hado ...
- webpack踩坑之路——构建基本的React+ES6项目
转自:http://www.cnblogs.com/ghost-xyx/p/5483464.html webpack是最近比较火的构建工具,搭配上同样比较火的ReacJS与ES6(ES2015)一定是 ...
- webpack踩坑之路——图片的路径与打包
转自:http://www.cnblogs.com/ghost-xyx/p/5812902.html 刚开始用webpack的同学很容易掉进图片打包这个坑里,比如打包出来的图片地址不对或者有的图片并不 ...
- 踩坑之路_"var name = ' ';"_迷之BUG
情景介绍:最近写一个拖拽生成图表的工具,自己的思路每次mousedown的时候动态将this的name属性值赋值给全局中变量(自己手贱测试时直接将变量名命名为了'name',一大波bug还有30s到达 ...
随机推荐
- SpringCloud微服务实战——搭建企业级开发框架(四十八):【移动开发】整合uni-app搭建移动端快速开发框架-使用第三方UI框架
uni-app默认使用uni-ui全端兼容的.高性能UI框架,在我们开发过程中可以满足大部分的需求了,并且如果是为了兼容性,还是强烈建议使用uni-ui作为UI框架使用. 如果作为初创公司,自 ...
- Python--网络编程学习笔记系列02 附:tcp服务端,tcp客户端
Python--网络编程学习笔记系列02 TCP和UDP的概述: udp通信模型类似于写信,不需要建立相关链接,只需要发送数据即可(现在几乎不用:不稳定,不安全) tcp通信模型类似于打电话,一定要建 ...
- 表单的子元素可不在form标签内
表单是网页用于向服务器发送数据的元素.其用法类似下面: <form method="POST" action="/login"> <input ...
- Xtrabackup使用帮助
目录 1.安装工具 2.下载后上传到需要备份的服务器 全备 1.安装完成后我们进行数据库备份执行以下命令 2.查看备份的数据 3.进入数据库,删除一个测试库 4.删除school库 5.备份数据目录 ...
- RobotFrameWork基础一
1.变量: 作用域: Set Global Variables:设定全局级变量 Set Suite Variables: 设定Test Suite 级变量 Set Test ...
- Go语言核心36讲24
你好,我是郝林,今天我们继续来聊聊panic函数.recover函数以及defer语句的内容. 我在前一篇文章提到过这样一个说法,panic之中可以包含一个值,用于简要解释引发此panic的原因. 如 ...
- <二>自己实现简单的string
我们结合运算符重载知识实现string 类 在自己实现的String类中可以参考C++中string的方法 例如构造,加法,大小比较,长度,[] 等操作. 当前的MyString 类中,暂时不加入迭代 ...
- 第2-4-6章 springboot整合规则引擎Drools-业务规则管理系统-组件化-中台
目录 7. Spring整合Drools 7.1 Spring简单整合Drools 7.1.1 以上代码均在drools_spring项目中 7.2 Spring整合Drools+web 7.2 以上 ...
- Kubernetes(K8S) 配置管理-ConfigMap 介绍
作用:存储不加密数据到 etcd,让 Pod 以变量或者 Volume 挂载到容器中 场景:配置文件 创建配置文件 redis.properties redis.host=127.0.0.1 redi ...
- 漫谈计算机网络:应用层 ----- 从DNS域名解析到WWW万维网再到P2P应用
2022-12-04 18:31:01 纪念一下博主的<漫谈计算机网络>连载博客 浏览量破500了! 今天更新完结篇! 面试答不上?计网很枯燥? 听说你学习 计网 每次记了都会忘? 不妨抽 ...