随手记 React 总结

CSS normalize

adding CSS Reset

在 index.css 中添加 @import-normalize; 即可。

作用是保证页面在不同浏览器上默认样式相近。

SCSS

adding Sass Stylesheets

yarn add node-sass@npm:sass

node-sass,下载速度慢、本地编译慢。

react 只支持 node-sacc 不支持 dart-sass。

npm 6.9 支持一个新功能,叫做 package alias。node-sass@npm:sass

CSS @import 引用

Vue 项目中干用 @ 表示 src/ 目录。

React 直接写目录即可。

JS 配置:在 tsconfig.json 中添加一句 "baseUrl": "src"文档

在 Webstorm 中将 src 目录标记为 resource root 。

helper.scss

创建 helper.css,helper.scss 里放置变量、函数等公用的东西。

CSS-in-JS

styled-components

1
2
yarn add styled-components
yarn add --dev @types/styled-components

或者用 css-module

使用:

1
2
3
const AppWrapper = styled.div`
color: #333;
`;

初始化目录如下

Snipaste_2022-01-20_23-03-02

React Router

本项目一共4个页面:

1
2
3
4
5
6
#/money 记账页
#/labels 标签页
#/statistics 统计页
404 页

默认进入 #/money

安装

1
2
yarn add react-router-dom@6
yarn add --dev @types/react-router-dom@6

文档

报错: TS2305: Module ‘“react-router-dom”‘ has no exported member ‘Switch’.

解决: Use Routes instead of Switch

V5 -> V6 的变化

本项目使用 V6

V6
1
2
3
4
5
<Routes>
<Route path="/" element={<Home/>}/>
<Route path="/users" element={<Users/>}/>
<Route path="/about" element={<About/>}/>
</Routes>
V5
1
2
3
4
5
6
7
8
9
10
11
 <Switch>
<Route path="/about">
<About />
</Route>
<Route path="/users">
<Users />
</Route>
<Route path="/">
<Home />
</Route>
</Switch>

设置 React Router 默认路由重定向到 /money

v6

1
<Route path="/" element={<Navigate replace to="/money"/>}/>

v5

1
<Redirect exact from="/" to="/money" />

Hash Router

1
import { BrowserRouter as Router } from 'react-router-dom';

改为

1
import { HashRouter as Router,} from 'react-router-dom';

引入 svg

需要配置,svg-sprite-loadersvgo-loader

1
2
3
yarn eject // 拿到 webpack 配置
yarn add --dev svgo-loader
yarn add --dev svg-sprite-loader

在 config/webpack.config.js 中添加如下代码:

1
2
3
4
5
6
7
{
test: /\.svg$/,
use: [
{ loader: 'svg-sprite-loader', options: {} },
{ loader: 'svgo-loader', options: {} }
]
}
Snipaste_2022-01-22_20-30-01

Icon

使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 直接引用即可
import 'icons/money.svg';

require('icons/money.svg');

<svg className="icon">
<use xlinkHref="#labels"/>
</svg>
<svg className="icon">
<use xlinkHref="#morney"/>
</svg>
<svg className="icon">
<use xlinkHref="#statistics"/>
</svg>

优化:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 优化抽离为 Icon.tsx

import React from 'react';
import 'icons/money.svg';
import 'icons/labels.svg';
import 'icons/statistics.svg';

type Props = {
name: string
}

const Icon = (props: Props) => {
return (
<svg className="icon">
<use xlinkHref={'#' + props.name}/>
</svg>
);
};

export default Icon;

// 使用
<Icon name="labels"/>
<Icon name="money"/>
<Icon name="statistics"/>

优化一个个导入 svg

在 Icon.tsx 中添加

1
2
3
let importAll = (requireContext: __WebpackModuleApi.RequireContext) => requireContext.keys().forEach(requireContext);
try {importAll(require.context('../icons', true, /\.svg$/));} catch (e) { console.log(e); }

报错修复:yarn add --dev @types/webpack-env 文档

将 svg 抽离成 <Icon /> 组件

将导航栏抽离成 <Nav /> 组件

将布局抽离成 <Layout /> 组件

文档

在 config/webpack.config.js 中:

1
2
3
4
5
6
7
8
9
10
11
{
test: /\.svg$/,
use: [
{ loader: 'svg-sprite-loader', options: {} },
{ loader: 'svgo-loader', options: {
plugins: [
{ removeAttrs: { attrs: 'fill' } }
]
} }
]
}

NavLink

1
2
3
4
5
<NavLink to="/labels"
className={({isActive}) => (isActive ? 'active' : 'inactive')}>
<Icon name="labels"/>
标签
</NavLink>

非受控/受控 input

受控

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const NoteSection = () => {
const [note, setNote] = useState('');

return (
<Wrapper>
<label>
<span>备注</span>
<input type="text" placeholder="输入备注信息"
value={note}
onChange={e => setNote(e.target.value)}/>
</label>
</Wrapper>
);
};

非受控,使用 ref

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
const NoteSection = () => {
const [note, setNote] = useState('');

const refInput = useRef<HTMLInputElement>(null);
const onBlur = () => {
if (refInput.current !== null) {
setNote(refInput.current.value);
}
};

return (
<Wrapper>
<label>
<span>备注</span>
<input type="text" placeholder="输入备注信息"
value={note}
ref={refInput}
onBlur={onBlur}/>
</label>
</Wrapper>
);
};

React onChange 会在输入一个字符的时候触发

HTML onChange 会在鼠标移走的时候触发,早于 onBlur

CategorySection 组件优化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const CategorySection = () => {
const [category, setCategory] = useState('-');
return (
<Wrapper>
<ul>
<li className={category === '-' ? 'selected' : ''}
onClick={() => setCategory('-')}>支出
</li>
<li className={category === '+' ? 'selected' : ''}
onClick={() => setCategory('+')}>收入
</li>
</ul>
</Wrapper>
);
};

优化后:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const CategorySection = () => {
const categoryMap = {'-': '支出', '+': '收入'};
type Keys = keyof typeof categoryMap
const [categoryList] = useState<Keys[]>(['-', '+']);
const [category, setCategory] = useState('-');

return (
<Wrapper>
<ul>
{categoryList.map(c =>
<li className={category === c ? 'selected' : ''}
onClick={() => setCategory(c)}
>{categoryMap[c]}
</li>
)}
</ul>
</Wrapper>
);
};

合并 className

1
2
yarn add classnames
yarn add --dev @types/classnames

demo:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import React from 'react';
import cs from 'classnames';

let importAll = (requireContext: __WebpackModuleApi.RequireContext) => requireContext.keys().forEach(requireContext);
try {importAll(require.context('../icons', true, /\.svg$/));} catch (e) { console.log(e); }

type Props = {
name?: string;
} & React.SVGAttributes<SVGElement>

const Icon = (props: Props) => {
const {name, children, className, ...rest} = props;
return (
<svg className={cs('icon', className)} {...rest}>
{props.name && <use xlinkHref={'#' + props.name}/>}
</svg>
);
};

export default Icon;

部署

在 package.json 中添加

"homepage": "/morney-r-website",

文档

再运行 yarn build