仿-问卷星
一、创建项目
yarn create react-app wenjuanxing --template typescript
yarn create vite wenjuanxing --template react-ts
代码规范
高质量代码的特点
- 严格编码规范(靠工具、流程,而非自觉)
- 合理、规范的注释
- 代码合理拆分
两者区别
eslint prettier
- eslint 编码规范,如变量未定义(语法语义)
- prettier 编码风格,如末尾是否用
;
- eslint 也有编码风格的功能,两者可能会有冲突
eslint
安装插件
npm install eslint @typescript-eslint/parser @typescript-eslint/eslint-plugin -save-dev
初始化配置文件 .eslint.js
npx eslint --init ## 然后根据引导一步一步走
解释:eslint plugin
与 extend
的区别:
-
extend
提供的是 eslint 现有规则的一系列预设 -
plugin
则提供了除预设之外的自定义规则,当你在 eslint 的规则里找不到合适的的时候就可以借用插件来实现了
安装 vscode 插件 eslint
,此时就可以看到代码 App.txs
中的错误提示(如定义一个未使用的变量)
在 package.json
中增加 scripts "lint": " eslint 'src/**/*.+(js|ts|jsx|tsx)' "
控制台运行 npm run lint
也可以看到错误提示。如果要自动修复,可以加 --fix
参数
prettier
npm install prettier eslint-config-prettier eslint-plugin-prettier -save-dev
-
eslint-config-prettier
禁用所有和 Prettier 产生冲突的规则 -
eslint-plugin-prettier
把 Prettier 应用到 Eslint,配合 rules"prettier/prettier": "error"
实现 Eslint 提醒。
在 eslint 配置文件的 extends
最后 增加 'plugin:prettier/recommended'
安装 vscode 插件 prettier
,此时可以看到代码 App.txs
中的格式提示(如末尾是否使用 ;
,或单引号、双引号)
在 package.json
中增加 scripts "format": " prettier --write 'src/**/*.+(js|ts|jsx|tsx)' "
控制台运行 npm run format
可以修复所有的格式错误
设置 vscode .vscode/settings.json
自动保存格式,可以在文件保存时,自动保留格式
{
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true
}
}
增加配置文件 .prettierrc.js
规定自己的编码格式,运行 npm run format
可以看到效果,保存文件也可以看到效果。
【注意】如果此处没效果,可以重启 vscode 再重试。
一直搞不定,重启 vscode 就好了。
在 vscode 搜“prettier” 插件时,发现一个 “reload required” 的提示,于是就重启了
CRA 创建的项目,配置文件是 js
格式
vite 创建的项目,配置文件是 cjs
格式
提交到 git 仓库
PS:git 不会从 0 讲起
选择平台
- 工作中,按照公司规定,可能有内网 git 仓库
- 正式的开源项目,需要积累 star ,可以考虑 github (但有时访问不稳定,不同网络环境不一样)
- 个人学习项目,尽量选择国内平台,速度快
我选择 coding.net
演示
git remote add origin xxx
git push -u origin master
husky
git-hook
安装依赖
npm install husky -save-dev
参考文档 github.com/typicode/hu… 增加三个 pre-commit
命令
npm run lint
npm run format
git add .
可以故意制造一个错误:定义一个未使用变量(eslint 配置文件 rules
增加 'no-unused-vars': 'error',
)
然后执行 git commit
试试
commit-lint
参考文档 github.com/conventiona… 安装设置即可
commit 规则查看 node_modules/@commitlint/config-conventional
(在 commitlint.config.js
中有配置)
尝试 git commit -m "test"
会失败,再尝试 git commit -m "chore: commit lint"
会成功
加餐:vite 和 webpack
PS:加餐课程,选学
CRA 使用 webpack 作为打包工具,和 vite 是竞品。
区别
- vite 启动速度更快,使用
ES Module
语法,开发环境节省打包过程 - CRA 或 webpack 使用者更多,时间更久,插件更丰富
我们选择 CRA ,先考虑稳定、开发成本,再说效率
代码演示
ES Module 代码演示,参考 es-module-demo
CRA demo 和 vite demo 启动之后,看浏览器控制台 source
二、JSX
JSX - JS 语法扩展,可以在 JS 中写模板(类似 HTML 语法)
JSX 已经成为 ES 语法标准,也可用于其他框架,如 Vue3
- 标签
- 属性
- 事件
- JS 表达式
- 判断
- 循环
标签
- 首字母小写 - HTML tag
- 首字母大写 - 自定义组件
- 如
<input/>
和<Input/>
就不一样 可以像 HTML 一样嵌套
JSX 里的标签必须是闭合的,<input>
<br>
这样写在 JSX 会报错(在 HTML 中不会报错),必须闭合 <input/>
每一段 JSX 只能有一个根节点,或者使用 <></>
( Fragment )
属性
和 HTML 属性基本一样,但有些和 JS 关键字冲突了
-
class
要改为className
-
style
要写成 JS 对象(不能是 string),key 采用驼峰写法 -
for
要改为htmlFor
事件
onXxx
的形式
注意 TS 的写法
function clickHandler(event: React.MouseEvent<HTMLParagraphElement>) {
event.preventDefault()
console.log('clicked')
}
return <p onClick={clickHandler}>hello world</p>
如果要想传递参数,可以通过如下方式
function clickHandler(event: React.MouseEvent<HTMLParagraphElement>, x: string) {
event.preventDefault()
console.log('clicked', x)
}
return (
<p onClick={(e: React.MouseEvent<HTMLParagraphElement>) => clickHandler(e, 'hello')}>
hello world
</p>
)
PS:Event handlers must be passed, not called! onClick={handleClick}
, not onClick={handleClick()}
.
JS 表达式
{xxx}
格式表示一个 JS 变量或表达式,可用于
- 普通文本内容,或判断、循环
- 属性值
- 用于注释
判断
JS 一般使用 if...else
做判断,但不能用于 JSX 的 {xxx}
中。
所以,可以选择其他方式做判断
- 运算符
&&
- 三元表达式
a ? b : c
- 用函数封装
const flag = true
return <div>
{flag && <p>hello</p>}
{flag ? <p>你好</p> : <p>再见</p>}
</div>
或者用函数封装
function Hello() {
if (flag) return <p>你好</p>
else return <p>再见</p>
}
return <Hello></Hello>
循环
使用 map
做循环
const list = [
{ username: 'zhangsan', name: '张三' },
{ username: 'lisi', name: '李四' },
{ username: 'shuangyue', name: '双越' },
]
const ul = <ul>
{list.map(user => {
return <li key={user.username}>{user.name}</li>
})}
</ul>
JSX 循环必须有 key
- 帮助 React 识别哪些元素改变了,比如被添加或删除。
- 同级别
key
必须唯一 -
key
是不可改变的 —— 尽量不用 index ,要用业务 ID (也不要用随机数) -
key
用于优化 VDOM diff 算法(后面再说)
显示 HTML 代码
JSX 防止注入攻击,否则用 dangerouslySetInnerHTML={{ __html: 'xxx' }}
三、组件和 props
React 一切皆组件
React apps are made out of components. A component is a piece of the UI (user interface) that has its own logic and appearance. A component can be as small as a button, or as large as an entire page.
组件可嵌套
- React 通过组件来构建 UI
- 组件拆分也有利于代码组织和维护,尤其对于大型软件
- JSX 中,组件 tag 首字母要大写
代码演示:从 index.tsx 开始,到 <App>
全都是组件。
组件就是一个函数
- React 之前是 class 组件
- 现已被函数组件 FC 全面取代
- 输入 props ,返回一段 JSX
实战:List 页面抽离组件
代码参考 react-ts-demo 中 components/QuestionCard1.tsx
- props 类型
- TS 泛型
进阶:type 还是 interface
都可以实现类型定义的功能 (具体代码演示),用哪个都可以
PS:组件之间的数据传递不仅仅只有 props ,课程后面还会继续讲解其他形式。
TS 语法如果一开始不熟练,就先记住当前的。随着课程深入,用多了也就熟练了。
PS:函数也可以当做属性来传递
JSX 对比 Vue 模板
列几个 JSX 和 Vue 模板的重大区别
- 判断,Vue 模板用
v-if
指令 cn.vuejs.org/guide/essen… - 循环,Vue 模板用
v-for
指令 cn.vuejs.org/guide/essen… - Vue 模板中没有
{xxx}
写法,全都是"xxx"
PS:Vue3 也能很友好的支持 JSX —— 从一开始抨击 JSX 到最后接纳
通过对比,可以看出 React 和 Vue 最初设计理念的区别
- React - JS 能实现的都交给 JS ,不重复定义 —— 要求使用者 JS 熟练
- Vue - 自定义很多指令和写法,初学者好理解好记忆,好推广 —— 这么多写法需要记忆、查文档,麻烦
PS:Vue 最初模仿的是 Angular ,Angular 是几个 Java 程序员开发的。他们对 JS 不熟练,才搞了这么多指令,方便自己使用。
四、Hooks
useState
让页面“动”起来
例如实现一个 click 计数功能,普通变量无法实现。即:修改普通变量无法触发组件的更新 rerender
通过 useState 即可实现。
state 是什么
State, A component's memory —— 这个比喻非常好!
- props 父组件传递过来的信息
- state 组件自己内部的状态,不对外
每次 state 变化,都会触发组件更新,从新渲染页面。
代码演示,参考 react-ts-demo 中 pages/StateDemo1.tsx
import React, { FC, useState } from 'react'
const Demo: FC = () => {
// let count = 0 // 普通的 js 变量,无法触发组件的更新
const [count, setCount] = useState(0) // useState 可以触发组件的更新,
// const [name, setName] = useState('双越')
function add() {
// count++
// setCount(count + 1)
setCount(count => count + 1) // 使用函数,state 更新不会被合并
setCount(count => count + 1)
setCount(count => count + 1)
setCount(count => count + 1)
setCount(count => count + 1)
// setCount(count => count + 1)
console.log('cur count ', count) // 异步更新,无法直接拿到最新的 state 值
// setName('x')
// console.log(name) // 如果说一个变量,不用于JSX中显示,那就不要用useState来管理,用useRef。
}
return (
<div>
<button onClick={add}>add {count}</button>
</div>
)
}
export default Demo
state 的特点
异步更新
代码演示
PS:setState 传入函数,可同步更新
可能会被合并
代码演示
不可变数据
state 可以是任意 JS 类型,不仅仅是值类型。
不可直接修改 state ,而要 setState 新值。
代码演示
import React, { FC, useState } from 'react'
const Demo: FC = () => {
// const [userInfo, setUserInfo] = useState({ name: '双越', age: 20 })
// function changeAge() {
// // **不可变数据** - 不去修改 state 的值,而是要传入一个新的值 —— 重要!
// setUserInfo({
// ...userInfo,
// age: 21,
// })
// }
const [list, setList] = useState(['x', 'y'])
function addItem() {
// **不可变数据** - 不去修改 state 的值,而是要传入一个新的值 —— 重要!
setList(list.concat('z'))
// setList([...list, 'z'])
}
return (
<div>
<h2>state 不可变数据</h2>
{/* <div>{JSON.stringify(userInfo)}</div>
<button onClick={changeAge}>change age</button> */}
<div>{JSON.stringify(list)}</div>
<button onClick={addItem}>add item</button>
</div>
)
}
PS:函数组件,每个更新函数从新执行,state 被重置,而不是被修改。state 可以理解为 readOnly
immer
Immer 简化了不可变数据结构的处理。特别是对于 JS 语法没那么熟悉的人。
代码演示,参考 react-ts-demo 中 pages/ImmerDemo1.tsx
实战:List 页面使用 state
- 使用 state
- 使用 immer
- push
- 修改 isPublish
代码参考 pages/List2.tsx
import React, { FC, useState, useEffect } from 'react'
import produce from 'immer'
import QuestionCard from './components/QuestionCard'
// 组件是一个函数(执行返回 JSX 片段),组件初次渲染执行这个函数
// 任何 state 更新,都会触发组件的更新(重新执行函数)
const List2: FC = () => {
useEffect(() => {
console.log('加载 ajax 网络请求')
return () => {
console.log('销毁')
}
}, []) // 无依赖,组件初次渲染时执行
// const [count, setCount] = useState(0)
const [questionList, setQuestionList] = useState([
{ id: 'q1', title: '问卷1', isPublished: false },
{ id: 'q2', title: '问卷2', isPublished: true },
{ id: 'q3', title: '问卷3', isPublished: false },
{ id: 'q4', title: '问卷4', isPublished: true },
])
// useEffect(() => {
// console.log('question list changed')
// }, [questionList])
// useEffect(() => {
// console.log('count changed')
// }, [count, questionList])
function add() {
// setCount(count + 1)
const r = Math.random().toString().slice(-3)
// setQuestionList(
// // 新增 concat
// questionList.concat({
// id: 'q' + r,
// title: '问卷' + r,
// isPublished: false,
// })
// )
// immer 的方式
setQuestionList(
produce(draft => {
draft.push({
id: 'q' + r,
title: '问卷' + r,
isPublished: false,
})
})
)
}
function deleteQuestion(id: string) {
// // 不可变数据
// setQuestionList(
// // 删除 filter
// questionList.filter(q => {
// if (q.id === id) return false
// else return true
// })
// )
// immer 的方式
setQuestionList(
produce(draft => {
const index = draft.findIndex(q => q.id === id)
draft.splice(index, 1)
})
)
}
function publishQuestion(id: string) {
// setQuestionList(
// // 修改 map
// questionList.map(q => {
// if (q.id !== id) return q
// return {
// ...q,
// isPublished: true,
// }
// })
// )
// immer 的方式
setQuestionList(
produce(draft => {
const q = draft.find(item => item.id === id)
if (q) q.isPublished = true
})
)
}
return (
<div>
<h1>问卷列表页2</h1>
<div>
{questionList.map(question => {
const { id, title, isPublished } = question
return (
<QuestionCard
key={id}
id={id}
title={title}
isPublished={isPublished}
deleteQuestion={deleteQuestion}
publishQuestion={publishQuestion}
/>
)
})}
</div>
<div>
<button onClick={add}>新增问卷</button>
</div>
</div>
)
}
export default List2
最重要的就是:不可变数据 —— 这是 React state 的核心
useEffect
副作用
干了自己不该干的事情,函数本来执行完就行。
函数组件:执行函数,返回 JSX
- 初次渲染时
- state 更新时
但有些场景需要如下功能
- 当渲染完成时,做某些事情
- 当某个 state 变化时,做某些事情
- 如 ajax 加载数据(state 变化重新加载)
如果只有 执行函数,返回 JSX
这个逻辑,无法满足上面的场景。
所以需要 useEffect
组件渲染完成时
代码演示:List2.tsx 使用 useEffect
模拟 ajax 请求
useEffect(() => {
console.log('加载 ajax 网络请求')
return () => {
console.log('销毁')
}
}, []) // 无依赖,组件初次渲染时执行
某些 state 更新时
代码演示:QuestionCard 监听 isPublished
的更新
useEffect(() => {
console.log('question list changed')
}, [questionList])
// 依赖两个state
useEffect(() => {
console.log('count changed')
}, [count, questionList])
组件销毁时
有创建就有销毁,有生就有死
代码演示:增加 isDeleted
属性和 delete
事件,看 QuestionCard 组件的销毁
【重要】如果有定时任务,或者 DOM 事件,组件销毁时一定要解绑
执行两次(销毁一次)
useEffect(() => {
console.log('question card mounted')
return () => {
console.log('question card unmounted', id) // 销毁
}
// 生命周期:创建,更新(state 变化),销毁
}, [])
React18 开始,useEffect
在开发环境下执行两次。
模拟组件挂载、销毁、重新挂载的完整流程,及早发现后续的问题。如果只挂载一次,有可能卸载组件时有问题。
而且,实际项目中某些组件真的有可能会被挂载很多次(如重置 state),要及早模拟这种情况,避免出现重复挂载的问题(如弹窗重复、bindEvent 重复)
生产环境下,不会再执行两次
其他 Hooks
useRef
- 一般用于操作 DOM 元素,代码演示:src/pages/UseRefDemo1.tsx
- useRef 也可以传入 JS 值,但更新时不会触发 rerender ,需替换为 useState
- 要和vue3 ref区分开(vue3 ref 用于操作DOM,用于响应式监听)
const Demo: FC = () => {
const inputRef = useRef<HTMLInputElement>(null)
function selectInput() {
const inputElem = inputRef.current // 当前指向的什么
if (inputElem) inputElem.select() // DOM 节点,DOM 操作 API
}
return (
<div>
<input ref={inputRef} defaultValue="hello world" />
<button onClick={selectInput}>选中 input</button>
</div>
)
}
const Demo: FC = () => {
const nameRef = useRef('双越') // 不是 DOM 节点,普通的 JS 变量
function changeName() {
nameRef.current = '双越老师' // 修改 ref 值,不会触发 rerender ( state 修改会触发组件 rerender )
// console.log(nameRef.current)
}
return (
<>
<p>name {nameRef.current}</p>
<div>
<button onClick={changeName}>change name</button>
</div>
</>
)
}
useMemo
- 函数组件,默认,每次 state 变化都会重新执行
- useMemo 可以缓存某个数据,不用每次都重新生成
- 可用于计算量比较大的数据场景
代码参考 pages/UseMemoAndCallback/UseMemoDemo1.tsx
import React, { FC, useMemo, useState } from 'react'
const Demo: FC = () => {
console.log('demo...')
const [num1, setNum1] = useState(10)
const [num2, setNum2] = useState(20)
const [text, setText] = useState('hello') // 更新,导致组件 rerender
const sum = useMemo(() => {
console.log('gen sum...') // 缓存
return num1 + num2
}, [num1, num2])
return (
<>
<p>{sum}</p>
<p>
{num1} <button onClick={() => setNum1(num1 + 1)}>add num1</button>
</p>
<p>
{num2} <button onClick={() => setNum2(num2 + 1)}>add num2</button>
</p>
<div>
{/* form 组件,受控组件 */}
<input onChange={e => setText(e.target.value)} value={text}></input>
</div>
</>
)
}
export default Demo
注意文档中的这段话 “你可以把 useMemo 作为性能优化的手段,但不要把它当成语义上的保证。” zh-hans.reactjs.org/docs/hooks-…
即,useMemo 的控制权在 React ,不一定保证每个都会缓存,但都是为了全局的性能最佳。
useCallback
useCallback 就是 useMemo 的语法糖,和 useMemo 一样。用于缓存函数。
代码参考 pages/UseMemoAndCallback/UseCallbackDemo1.tsx
import React, { FC, useState, useCallback } from 'react'
const Demo: FC = () => {
const [text, setText] = useState('hello')
const fn1 = () => console.log('fn1 text: ', text)
const fn2 = useCallback(() => {
console.log('fn2 text: ', text)
}, [text])
return (
<>
<div>
<button onClick={fn1}>fn1</button> <button onClick={fn2}>fn2</button>
</div>
<div>
{/* form 组件,受控组件 */}
<input onChange={e => setText(e.target.value)} value={text}></input>
</div>
</>
)
}
export default Demo
自定义 Hooks
已学习了几个常用的内置 Hooks ,可以用于很多业务功能。
修改网页标题
- 第一步,直接在组件内部写
- 第二步,可以抽离一个函数
- 第三步,可以直接抽离一个文件 src/hooks/useTitle.ts ,引用使用
抽离自定义 Hook ,可用于很多组件,复用代码
import { useEffect } from 'react'
function useTitle(title: string) {
useEffect(() => {
document.title = title
}, [])
}
export default useTitle
获取鼠标位置
(刚才的没有返回值,这次有返回值)
代码演示 src/hooks/useMousePosition.ts (直接在 App.tsx 中使用)
import { useState, useEffect, useCallback } from 'react'
// 获取鼠标位置(自定义 Hook)
function useMouse() {
const [x, setX] = useState(0)
const [y, setY] = useState(0)
const mouseMoveHandler = useCallback((event: MouseEvent) => {
setX(event.clientX)
setY(event.clientY)
}, [])
useEffect(() => {
// 监听鼠标事件
window.addEventListener('mousemove', mouseMoveHandler)
// 组件销毁时,一定要解绑 DOM 事件!!! (可能会出现组件内存泄漏问题)
return () => {
window.removeEventListener('mousemove', mouseMoveHandler)
}
}, [])
return { x, y }
}
export default useMouse
异步获取信息
(再来个异步的)
代码演示 src/hooks/useGetInfo.ts (直接在 App.tsx 中使用)
import { useState, useEffect } from 'react'
// 异步获取信息
function getInfo(): Promise<string> {
return new Promise(resolve => {
setTimeout(() => {
resolve(Date.now().toString())
}, 1500)
})
}
const useGetInfo = () => {
const [loading, setLoading] = useState(true)
const [info, setInfo] = useState('')
useEffect(() => {
getInfo().then(info => {
setLoading(false)
setInfo(info)
})
}, [])
return { loading, info }
}
export default useGetInfo
小节
自定义 Hooks 可以抽离公共逻辑,复用到多个组件中 —— 这是 Hooks 设计的初衷
在 Hooks 和函数组件之前,class 组件也有一些方法:mixin HOC render-prop 等,但都没有 Hooks 来的简单。
第三方 Hooks
回顾上一节的“三步”,其实还有第四步:抽离为单独的模块,发布到 npm ,供所有开发者使用。
例如,之前开发的 useTitle useMousePosition ,就现成的
- ahooks.js.org/zh-CN/hooks…
- ahooks.js.org/zh-CN/hooks…
ahooks
ahooks.js.org/zh-CN/
ahooks 是国内流行的第三方 Hooks 库
- 功能全面
- 使用简单
- 文档 demo 清晰易懂
后面会再次用到 ahooks ,到时候再代码演示
react-use
github.com/streamich/r…
react-use 是国外比较流行的,功能也很前面,但是英文文档。
先做了解吧,项目中不会使用。
Hooks 使用规则
命名规则
Hook 必须 useXxx
格式来命名。
PS:这种命名规则也很易读,简单粗暴
调用位置
Hook 或自定义 Hook ,只能在两个地方被调用
- 组件内部
- 其他 Hook 内部
组件外部,或一个普通函数中,不能调用 Hook
顺序一致
Hook 在每次渲染时都按照相同的顺序被调用。
- Hook 必须是组件“第一层代码”
- Hook 不可放在 if 等条件语句中 ( 或者前面有 return ,也算是条件 )
- Hook 不可放在 for 等循环语句中
代码演示
闭包陷阱
当异步函数中获取 state 时,可能不是最新的 state 值。
解决方案:替换为 useRef
—— 但 ref 变化不会触发 rerender ,所以得结合 state 一起
代码参考 src/pages/ClosureTrap.tsx
import React, { FC, useState, useRef, useEffect } from 'react'
const Demo: FC = () => {
const [count, setCount] = useState(0)
const countRef = useRef(0)
useEffect(() => {
countRef.current = count
}, [count])
function add() {
setCount(count + 1)
}
function alertFn() {
setTimeout(() => {
// alert(count) // count 值类型
alert(countRef.current) // ref 引用类型
}, 3000)
}
return (
<>
<p>闭包陷阱</p>
<div>
<span>{count}</span>
<button onClick={add}>add</button>
<button onClick={alertFn}>alert</button>
</div>
</>
)
}
export default Demo
五、使用CSS
普通 CSS
内联 style
- 和 HTML style 一样,元素的内联样式
- 必须是 JS 对象形式,不可以是字符串
- 样式名称用驼峰式写法,如
fontSize
代码演示
className
- 和 HTML class 一样,设置 CSS 样式名
- 和 JS
class
重复,所以改名className
- 可用
clsx
或classnames
条件判断
代码演示
// 第一种
let itemClassName = 'list-item'
if (isPublished) itemClassName += ' published'
// 逻辑稍微复杂
// 第二种
const itemClassName = classnames('list-item', { published: isPublished })
const itemClassName = classnames({
'list-item': true,
published: isPublished,
})
链接
- www.npmjs.com/package/cla…
- www.npmjs.com/package/cls…
尽量不用内联 style
- 内联 style 代码量多,性能差
- 外链样式(用 className)代码复用,性能好
- 这和 React 无关,在学 HTML CSS 时就知道
CSS Module
普通 CSS 的问题
- React 使用组件化
- 多个组件,对应多个 CSS
- 多个 CSS 就会造成命名重复,不好管理
CSS Module
- 每个 CSS 都是一个独立的模块,命名
xxx.module.css
- 每个模块中的 className 都不一样
- CRA 原生支持 CSS Module
代码演示,参考 components/Button2.tsx
// 第三种
import styles from './QuestionCard.module.scss'
const listItemClass = styles['list-item']
const publishedClass = styles.published
const itemClassName = classnames({
[listItemClass]: true,
[publishedClass]: isPublished,
})
使用 Sass
- CSS 写法比较原始
- 一般使用 Sass less 等预处理语言
- CRA 支持 Sass Module ,把后缀改为
.scss
即可
代码演示
CSS-in-js
- 在 JS 中(组件代码中)写 CSS
- 不用担心 CSS class 重名的问题
- CSS-in-js 是一个解决方案,并不是一个工具的名称
PS:CSS-in-js 并不是内联 style (重要!!!),它会经过工具的编译处理,生成 CSS class 的形式。
Styled-components
styled-components.com/
代码演示,参考 components/Button3.tsx
Styled-jsx
github.com/vercel/styl…
优点
CSS-in-js 能更灵活的支持动态样式,直接在 JS 中完成计算和样式切换。这比 css-module 更好。
实战 - List 页增加样式
选择 CSS Module
- 使用简单,学习成本低
- 性能更好,css-in-js 需要一定的编译时间
- 不需要那么多动态样式(css-in-js 的优点)
重构 List 页面 - 增加样式
UI 设计
代码演示
渐变背景色 color.oulu.me/
六、路由设计
页面对应的路由
- 首页
/
- 登录
/login
- 注册
/register
- 问卷管理
- 我的问卷
/manage/list
- 星标问卷
/manage/star
- 回收站
/manage/trash
- 问卷详情
- 编辑问卷
/question/edit/:id
(动态路由) - 问卷统计
/question/stat/:id
- 404
Layout 模板
- MainLayout
- ManageLayout
- QuestionLayout
开发
增加页面
-
pages/Home.tsx
-
pages/Login.tsx
-
pages/Register.tsx
-
pages/NotFoundPage.tsx
-
pages/manage/List.tsx
-
pages/manage/Star.tsx
-
pages/manage/Delete.tsx
-
pages/question/Edit.tsx
-
pages/question/Stat.tsx
增加 Layout
layouts 目录下,所有文件。
PS:先不要管 antd 组件和样式,先把 JSX 写出再说。
增加 React-router 配置
安装 react-router-dom
,配置参考 router/index.ts
还有 App.ts 也有改动
路由功能
- 跳转 - Home 页面使用
useNavigate
和<Link>
- 获取动态路由参数 - Edit 页面使用
useParams
- 获取 query - Home 页面使用
useSearchParams
七、UI组件库
认识 UI 组件库
- UI 组件库,封装了常用的 UI 组件,例如标题、导航、按钮、输入框等。
- 每个成熟的技术栈都有 UI 组件库,如 Vue elementUI ,React antd
- 一般是第三方开发的
React 常用 UI 组件库
- antDesign 国内最常用,现已发布到 v5 版本 an
上一篇: 问卷制作软件 - Nuggets
下一篇: 问卷星怎么设置翻页-掘金