1、四角边框怎么实现
利用线性渐变(linear-gradient)去实现
div {
width: 500px;
height: 500px;
border: 1px solid red;
background:
/* 左上角向下 */
linear-gradient(to bottom,
rgb(89, 134, 223) 0px,
rgb(89, 134, 223) 5px,
transparent 0px,
transparent 100%) left top no-repeat,
/* 左上角向右 */
linear-gradient(to right,
rgb(89, 134, 223) 0px,
rgb(89, 134, 223) 5px,
transparent 0px,
transparent 100%) left top no-repeat,
/* 右上角向下 */
linear-gradient(to bottom,
rgb(89, 134, 223) 0px,
rgb(89, 134, 223) 5px,
transparent 0px,
transparent 100%) right top no-repeat,
/* 右上角向左 */
linear-gradient(to left,
rgb(89, 134, 223) 0px,
rgb(89, 134, 223) 5px,
transparent 0px,
transparent 100%) right top no-repeat,
/* 左下角向上 */
linear-gradient(to top,
rgb(89, 134, 223) 0px,
rgb(89, 134, 223) 5px,
transparent 0px,
transparent 100%) left bottom no-repeat,
/* 左下角向右 */
linear-gradient(to right,
rgb(89, 134, 223) 0px,
rgb(89, 134, 223) 5px,
transparent 0px,
transparent 100%) left bottom no-repeat,
/* 右下角向上 */
linear-gradient(to top,
rgb(89, 134, 223) 0px,
rgb(89, 134, 223) 5px,
transparent 0px,
transparent 100%) right bottom no-repeat,
/* 右下角向左 */
linear-gradient(to left,
rgb(89, 134, 223) 0px,
rgb(89, 134, 223) 5px,
transparent 0px,
transparent 100%) right bottom no-repeat;
/* 控制渐变背景的尺寸大小 */
background-size: 2rem 1rem;
}
2、宽度自适应内容
使用 fit-content 属性去实现
<style>
.news {
background-color: #334e70;
padding: 5px 8px;
color: #fff;
font-size: 14px;
border-radius: 5px;
font-weight: 500;
display: flex;
align-items: center;
/* 宽度适应内容 */
width: fit-content;
}
a {
font-size: 12px;
margin-left: 5px;
color: #9c9797;
transform: translateY(10%);
}
</style>
<div class='news'>
<span>热门话题</span>
<a>更多</a>
</div>
3、antd 中,对于表单数据的管理
使用form 去维护所有的表单数据,antd 底层维护表单数据其实也是通过rc-form 这个第三方库实现的
getFieldError: 获取对应字段名的错误信息
getFieldInstance: 获取对应字段实例
getFieldsError: 获取一组字段名对应的错误信息,返回为数组形式
getFieldsValue: 获取一组字段名对应的值,会按照对应结构返回。默认返回现存字段值,当调用 `getFieldsValue(true)` 时返回所有值
getFieldValue: 获取对应字段名的值
isFieldsTouched: 检查一组字段是否被用户操作过,`allTouched` 为 `true` 时检查是否所有字段都被操作过
isFieldTouched: 检查对应字段是否被用户操作过
isFieldValidating: 检查对应字段是否正在校验
resetFields: 重置一组字段到 `initialValues`
scrollToField: 滚动到对应字段位置
setFields: 设置一组字段状态,可以给对应的表单统一设置一组状态(value,error...)
setFieldValue: 设置对应表单的值(该值将直接传入 form store 中。如果你不希望传入对象被修改,请克隆后传入)
setFieldsValue: 设置一组表单的值(该值将直接传入 form store 中。如果你不希望传入对象被修改,请克隆后传入)。
如果你只想修改 Form.List 中单项值,请通过 `setFieldValue` 进行指定
submit: 提交表单,与点击 `submit` 按钮效果相同
validateFields: 触发表单验证
antd3 中如果需要使用表单之外非用户输入的状态数据,可以先使用getFieldDecorator 注册表单字段,
然后form 就会把该状态维护到表单里面了,就可以使用form 提供的相关api 去操作该字段状态
antd4+ 中则可以直接使用Form.Item 注册(<Form.Item name='nameKey' />),getFieldDecorator 在antd4 已经被废弃了,
注意Form.Item 需要在Form 里面进行注册(<Form form={form}><Form.Item name='nameKey' /></Form>)
4、antd 中使用密码框,解决浏览器会自动填充的问题
给input 添加该属性
autoComplete='off' / autoComplete='new-password'
5、react 中状态透传的小坑
场景:
子组件的某一状态是依赖于父组件透传下来的状态,父组件状态更新,子组件会重渲染;但是子组件初始化的状态是依据父组件传下的状态,二次渲染的时候子组件的状态并没有被重新依据最新透传下来的状态进行初始化,而是依旧沿用第一次render 的数据
解决:
使用useEffect 监听props,重新set 子组件状态
const Demo = (props: any) => {
const { data } = props;
const [demo, setDemo] = useState<any>(data);
useEffect(() => {
setDemo(data)
}, [data]);
return <div>{demo}</div>
}
6、关于复杂类型数据的坑和妙用
问题
复杂类型数据就是对象,也就是引用数据。所谓引用数据,变量所存储的都是引用数据所指向的内存地址,存储的值都指向同一引用中的堆内数据。赋值操作并不是把值赋予了新变量,而是将引用地址赋予了新变量,指向的数据依旧是同一数据源;所以互相修改数据会存在引用污染,要杜绝这种情况就需要将数据进行拷贝,拷贝数据源,开辟新的堆内存进行存储。
场景
对于列表渲染的数据,我们需要对每一项数据进行修改:
- 利用引用关系,直接修改值(违背了数据不可变的原则,容易造成数据源混乱,引用注入;但是方便,在数据量很大,数据结构复杂的情况下,可以利用引用关系对数据进行操作)
- 利用每项数据唯一的标识,找到对应的数据,对数据进行拷贝修改,然后将数据进行整合,重新赋值,更新数据源(遵循数据的不可变原则,数据可读性好,来源清晰,不过操作繁琐,需要遍历数据进行操作)
- 有时候在复杂的表数据里面对数据进行操作,利用引用或许是一个不错的选择,但是不推荐,不过可以使用immer 对状态数据进行修改
- 注:Immer 总是会根据你对
draft
的修改来从头开始构建下一个 state。这使得你的事件处理程序非常的简洁,同时也不会直接修改 state
// 1、引用关系
const App: React.FC<any> = () => {
const [data] = useState<any>([
{ name: '小明', age: 28 },
{ name: '小北', age: 20 },
{ name: '小钰', age: 18 },
{ name: '小红', age: 25 },
]);
const [_, render] = useState(false);
return data.map((item: any, index: number) => {
return (
<div key={index}>
<span>name: {item.name}</span>
<span
onClick={() => {
item.age++;
render(b => !b);
}}
>age: {item.age}</span>
</div>
);
})
}
// 2、遍历拷贝
const App: React.FC<any> = () => {
const [data, setData] = useState<any>([
{ name: '小明', age: 28 },
{ name: '小北', age: 20 },
{ name: '小钰', age: 18 },
{ name: '小红', age: 25 },
]);
return data.map((item: any, index: number) => {
return (
<div key={index}>
<span>name: {item.name}</span>
<span
onClick={() => {
const newData = data.map((i: any, idx: number) => {
if (index === idx) {
return { ...i, age: i.age + 1 };
}
return i;
});
setData(newData);
}}
>age: {item.age}</span>
</div>
);
})
}
// 3、使用immer
import { useState } from 'react';
import { useImmer } from 'use-immer';
let nextId = 3;
const initialList = [
{ id: 0, title: 'Big Bellies', seen: false },
{ id: 1, title: 'Lunar Landscape', seen: false },
{ id: 2, title: 'Terracotta Army', seen: true },
];
export default function BucketList() {
const [myList, updateMyList] = useImmer(initialList);
const [yourList, updateYourList] = useImmer(initialList);
function handleToggleMyList(id, nextSeen) {
updateMyList(draft => {
const artwork = draft.find(a =>
a.id === id
);
artwork.seen = nextSeen;
});
}
function handleToggleYourList(artworkId, nextSeen) {
updateYourList(draft => {
const artwork = draft.find(a =>
a.id === artworkId
);
artwork.seen = nextSeen;
});
}
return (
<>
<h1>艺术愿望清单</h1>
<h2>我想看的艺术清单:</h2>
<ItemList
artworks={myList}
onToggle={handleToggleMyList} />
<h2>你想看的艺术清单:</h2>
<ItemList
artworks={yourList}
onToggle={handleToggleYourList} />
</>
);
}
function ItemList({ artworks, onToggle }) {
return (
<ul>
{artworks.map(artwork => (
<li key={artwork.id}>
<label>
<input
type="checkbox"
checked={artwork.seen}
onChange={e => {
onToggle(
artwork.id,
e.target.checked
);
}}
/>
{artwork.title}
</label>
</li>
))}
</ul>
);
}
快速剔除一个引用数据不必要的字段
利用解构 + 剩余参数
const data = { name: '你好', age: '666', sex: '男' };
const { sex, ...other } = data;
// other => { name: '你好', age: '666' }
other.age = 999;
// other => { name: '你好', age: 999 }
// data => { name: '你好', age: '666', sex: '男' }
关于引用关系的一个小坑
map、filter 等操作产生的新的数据,只是浅拷贝,直接修改深层数据,依旧会造成数据污染,影响到源数据
const data = [
{name: '小明', age: 18},
{name: '小北', age: 28},
];
const newData = data.map(i => i)
// data => [{name: '小明', age: 18}, {name: '小北', age: 28}]
// newData => [{name: '小明', age: 18}, {name: '小北', age: 28}]
newData.push({name: '小红', age: 30})
// data => [{name: '小明', age: 18}1: {name: '小北', age: 28}]
// newData => [{name: '小明', age: 18}, {name: '小北', age: 28}, {name: '小红', age: 30}]
newData[0].age = 99
// 影响了源数据data
// data => [{name: '小明', age: 99}, {name: '小北', age: 28}]
// newData => [{name: '小明', age: 99}, {name: '小北', age: 28}, {name: '小红', age: 30}]
7、画三角形
利用css3 的clip-path
-webkit-clip-path: polygon(50% 0%, 0% 100%, 100% 100%);
clip-path: polygon(50% 0%, 0% 100%, 100% 100%);
8、关于antd 中的Input 组件
defaultValue 只认初始化渲染的数据,后续数据的改变,无法影响defaultValue
9、页面切换,重新进入页面触发事件(visibilitychange)
/** 跳转页面回来强制刷新 */
useEffect(() => {
document.addEventListener('visibilitychange', () => {
(document.visibilityState === 'visible') && setIsUpdate(i => !i)
})
return () => {
document.removeEventListener('visibilitychange', () => (''))
}
}, [])
10、对数值或者是字符串数值的取整操作
/**
* ~~
* 类似于parseInt 可以把数值、字符转化成整数
*/
console.log(~~'52.99') // 52
/**
* parseInt()
* 把数值、字符转化成整数
*/
console.log(Number.parseInt('52.99')) // 52
/**
* floor()
* 向下取整
*/
console.log(Math.floor('52.99')) // 52
11、PC端内嵌网页(iframe)
HTML 内联框架元素 (<iframe>
) 表示嵌套的浏览内容,它能够将另一个 HTML 页面嵌入到当前页面中。
嵌套iframe 怎么获取顶层域的window 对象(window.top)
// 获取mk 系统中的全局变量里的一个状态标识
const platform = window.top ? (window.top as any)?.mk.getSysConfig('platform') : mk.getSysConfig('platform')
12、H5页面嵌套(WebView)
Android 内置webkit 内核的高性能浏览器,而WebView 则是在这个基础上进行封装后的一个控件,WebView 直译网页视图,我们可以简单的看作一个可以嵌套到界面上的一个浏览器控件!
通信方式
原生向H5 通信,可通过浏览器的url 进行query 传参;H5 向原生通信需要两边配合,使用postMessage 进行通信
13、表示颜色透明
- 透明度为0 => opacity: 0;
- 使用rgba => rgba(0, 0, 0, 0);
- 使用透明属性 => color: transparent;
14、react 中层级较深的组件数据透传
使用useContext() 和useReducer(),子孙组件向父级通信,采用上下文透传dispatch,避免层层回调
// 创建上下文对象
const TodosContext = React.createContext<any>(null)
/***************** 父组件 *******************/
const Counter = () => {
// const [state: 状态数据, dispatch: 派发action 的dispatch] = useReducer(reducer: reducer 函数, initialState: 初始化的state)
const [state, dispatch] = useReducer((preState: any, action: { type: string; payload: number }) => {
const { type, payload } = action
switch (type) {
case 'increment':
return { ...preState, count: preState.count + payload }
case 'decrement':
return { ...preState, count: preState.count - payload }
default:
return { ...preState }
}
}, { count: 10 } as any)
return (
<>
Count: {state.count}
<button onClick={() => dispatch({type: 'decrement', payload: 5 })}>-</button>
<button onClick={() => dispatch({type: 'increment', payload: 15 })}>+</button>
<TodosContext.Provider value={dispatch}>
<DeepTree state={state} TodosContext={TodosContext} />
</TodosContext.Provider>
</>
);
}
/***************** 子组件 *******************/
interface IDispatch {
type: string
payload: number
}
const DeepTree: any = ({ state, TodosContext }) => {
const dispatch: (args: IDispatch) => void = useContext(TodosContext)
return (
<Fragment>
<h1>{state?.count}</h1>
<button onClick={() => dispatch({ type: 'increment', payload: 5 })}>+</button>
<button onClick={() => dispatch({ type: 'decrement', payload: 10 })}>-</button>
<button onClick={() => dispatch({ type: 'other' })}>其他</button>
</Fragment>
)
}
15、Sass 的使用
@import "~@elements-toolkit/scss/common.scss";
$prefixCls: user-elem-swj-datacard2-x64a8; // 定义变量
.#{$prefixCls} { // #{$xxx} 插值语法
display: block;
text-decoration: none;
position: relative;
&-item { // &- 继承上级,进行拼接 => user-elem-swj-datacard2-x64a8-item
width: 19%;
height: 128px;
&-info {
margin: 0 0 0 16px;
.count {
font-size: 24px;
color: #2D3E50;
font-weight: 700;
margin-right: 2px;
}
.unit {
font-size: 16px;
color: #2D3E50;
font-weight: 400;
}
}
}
}
// 混入mixin
@mixin theme($theme: DarkGray) {
background: $theme;
box-shadow: 0 0 1px rgba($theme, .25);
color: #fff;
}
.info {
@include theme;
}
.alert {
@include theme($theme: DarkRed);
}
.success {
@include theme($theme: DarkGreen);
}
16、关于换行 white-space
white-space 设置或检索对象内文本显示方式,通常我们使用于强制一行显示内容。
我们可以使用white-space: nowrap 强制文本文字内容不换行,在对象内一行显示完所有文字内容。
17、嗅探方法,识别客户端设备
// 表示当前是否在浏览器环境中运行(即非 Node.js 环境)
const inBrowser = typeof window !== 'undefined';
// 表示当前是否在 Weex 环境中运行(即非浏览器环境)。
const inWeex = typeof WXEnvironment !== 'undefined' && !!WXEnvironment.platform;
// 表示当前的 Weex 平台类型(如 `android` 或 `ios`)。如果不在 Weex 环境中,则该变量值为undefined
const weexPlatform = inWeex && WXEnvironment.platform.toLowerCase();
// 表示当前浏览器的 user-agent 字符串,转换成小写字母后得到。
const UA = inBrowser && window.navigator.userAgent.toLowerCase();
// IE 浏览器
const isIE = UA && /msie|trident/.test(UA);
// IE9
const isIE9 = UA && UA.indexOf('msie 9.0') > 0;
// Edge 浏览器
const isEdge = UA && UA.indexOf('edge/') > 0;
// 安卓系统
const isAndroid = (UA && UA.indexOf('android') > 0) || (weexPlatform === 'android');
// IOS系统
const isIOS = (UA && /iphone|ipad|ipod|ios/.test(UA)) || (weexPlatform === 'ios');
// 谷歌浏览器
const isChrome = UA && /chrome\/\d+/.test(UA) && !isEdge;
// 表示当前浏览器是否为 PhantomJS
const isPhantomJS = UA && /phantomjs/.test(UA);
// 表示当前浏览器的 Firefox 版本号。如果当前浏览器不是 Firefox,则该变量值为 null
const isFF = UA && UA.match(/firefox\/(\d+)/);
18、适配问题,响应式布局
/**
* 1、根据屏幕宽度动态设置根字体大小,rem布局
*/
const recalculate = () => {
// 获取html 元素
const docElement = document.documentElement || document.body
// 获取可视窗口宽度大小
let clientWidth = docElement.clientWidth
// 默认pc 端的设计稿宽度
let designWidth = 1440
if (clientWidth < 750) {
// 屏幕太小则使用移动端的设计稿宽度
designWidth = 640
} else if (clientWidth < designWidth) {
clientWidth -= 80
} else {
clientWidth = designWidth
}
docElement.style.fontSize = (clientWidth / designWidth) * 100 + 'px'
}
window.addEventListener('resize', recalculate)
/**
* 2、针对组件的适配问题,基于组件开发,但是项目主体未做适配不可控,采用媒体查询,选择性渲染两套样式,大屏适配
*/
// 可视屏幕的宽度最大为2300px,否则采用另一套样式
@media screen and (max-width: 2300px) {
body,
html {
font-size: 16px;
}
}
// 可视屏幕的宽度最低达到2300px,否则采用另一套样式
@media screen and (min-width: 2300px) {
body,
html {
font-size: calc(46px / 2);
}
}
19、获取客户端分辨率
document.documentElement.clientWidth // 可视窗口的宽度
window.screen.width // 客户端的屏幕的宽度
window.devicePixelRatio // 客户端的缩放比
const screenWidth = window.screen.width * window.devicePixelRatio
20、监听分辨率做适配
const [screenWidth, setScreenWidth] = useState<number>(window.screen.width * window.devicePixelRatio)
useEffect(() => {
const throttle = (fn: () => void, time: number) => {
let startDate = Date.now()
return (...test: any) => {
const endDate = Date.now()
if (endDate - startDate >= time) {
fn.apply(this, test)
startDate = Date.now()
}
}
}
const onResize = throttle(() => {
setScreenWidth(window.screen.width * window.devicePixelRatio)
}, 1000)
window.addEventListener('resize', onResize)
return () => {
window.removeEventListener('resize', onResize)
}
}, [screenWidth])
21、滚动条样式
/* WebKit */
1、可以设置滚动条的宽度和高度
::-webkit-scrollbar {
width: 8px;
height: 8px;
}
2、可以设置滚动条的颜色和圆角
::-webkit-scrollbar-thumb {
background-color: #cccccc;
border-radius: 4px;
}
3、可以设置滚动条轨道的背景色
::-webkit-scrollbar-track {
background-color: #f0f0f0;
}
/* Firefox 火狐浏览器 */
scrollbar-color: #cccccc #f0f0f0;
scrollbar-width: thin;
22、对于url上的字符解析
encodeURI()
encodeURI()
函数通过将特定字符的每个实例替换为一个、两个、三或四转义序列来对统一资源标识符 (URI) 进行编码 (该字符的 UTF-8 编码仅为四转义序列) 由两个 “代理” 字符组成)。
encodeURI('123') // 123
encodeURI('钰见不止心动') // '%E9%92%B0%E8%A7%81%E4%B8%8D%E6%AD%A2%E5%BF%83%E5%8A%A8'
decodeURI()
decodeURI()
函数能解码由encodeURI
创建或其他流程得到的统一资源标识符(URI)。
decodeURI('123') // 123
decodeURI('%E9%92%B0%E8%A7%81%E4%B8%8D%E6%AD%A2%E5%BF%83%E5%8A%A8') // 钰见不止心动