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; `;
|
初始化目录如下
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-loader,svgo-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: {} } ] }
|
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
|
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 />
组件
修改 svg 颜色 <NavLink >
文档
在 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