学习了React后,总得实践一下不是。废话不多说,先上效果!
(掘金什么垃圾不能传视频他妈的)
(他妈的还加水印)
我接下来从以下几点介绍我这个项目:
- 糊UI
- 做动画/效果
- 持久存储
特别感谢Generative Pretrained Transformer,帮了我很多!
糊UI
以前我用过SwiftUI和Jetpack Compose,布局主要靠HStack VStack Column Row啥的,而且我还没接触过css。到了React里了,得写css啊!咋办呢?
调研(指问GPT)一番后,我决定使用grid来布局。
就,像这样把网格划好
.todo-item {
display: grid;
grid-template-columns: 50px auto;
grid-template-rows: auto auto;
}
然后把东西塞进去(省略了部分css):
interface TodoItemArgs {
title: string
content: string
done: boolean
setDone: (done: boolean) => void
}
const TodoItem: React.FC<TodoItemArgs> = ({ title , content, done, setDone }) => {
return (
<>
<div className="todo-item card" onClick={() => {
setDone(!done)
}}>
<div className='todo-checkbox'>
<CheckBox done={done} />
</div>
<h3 className='todo-title'>{title}</h3>
<p className='todo-content'>{content}</p>
</div>
</>
)
}
interface CheckboxArgs {
done: boolean
}
const CheckBox: React.FC<CheckboxArgs> = ({done}) => {
const r=10
const border=2
const dark=useMediaQuery('(prefers-color-scheme: dark)')
const light=!dark
// const foreground=light?"black":"white"
// const background=light?"white":"black"
return (
<>
<svg width={2*(r+border)} height={2*(r+border)}>
<circle r={r} cx={r+border} cy={r+border}
fill={done ? "#11DD11":"transparent"}
stroke={light?"#AAAAAA":"#AAAAAA"} stroke-width={border}
/>
</svg>
</>
)
}
.todo-checkbox {
grid-column: 1/2;
grid-row: 1/3;
}
.todo-title {
grid-column: 2;
grid-row: 1;
}
.todo-content {
grid-column: 2;
grid-row: 2;
}
其中,那个标记东西完没完成的小圈,是用svg写的。我挺不喜欢通过css奇技淫巧来画东西的,什么通过border+旋转实现一个勾这种东西,就让人很没有安全感,要稍微画复杂一点就画不出来了,而且还不好阅读。所以我选择了svg。
然后,东西就被塞进去了(废话)
再加一些全局的居中之类的代码,
#root {
text-align: center;
margin: 0 auto;
padding: 2rem;
}
差不多就把UI糊好了
动画和效果
从github的视频和上面的截图中可以看到,我为每个待办事项添加了阴影,而且鼠标经过的时候还会放大、阴影更深。这让画面好看了许多(自豪.jpg)。
阴影
这个其实非常简单,就一行css搞定了
box-shadow: 2px 2px 12px rgba(0,0,0,0.2);
圆角边框
一样简单
border-radius: 10px;
鼠标经过时调整
这里就是:
- 把阴影加深一点
- 让卡片变大一点
这样就让它像被抬起来了一样~
.card:hover {
box-shadow: 5px 5px 20px rgba(0,0,0,0.5);
transform: scale(1.05);
}
动画
阴影的话,我就直接选择了ease-out,先快后慢,挺舒服的。
放大的话,我想让他Q弹一点,于是只好选择使用贝塞尔曲线,让它先放大过头一点再弹回来,效果可好了!(不要脸+1)
用Firefox自带的工具调的曲线,感觉挺好用的。
就是web标准怎么不允许多几个锚点的曲线呢,,,,,,,,,……虽然我也可以不用
transition: box-shadow 0.15s ease-out,
transform 0.15s cubic-bezier(0,0,0.76,1.73);
持久化存储
偷了个懒,直接localStorage了,没咋优化性能,所有待办事项全部存一个位置。
然后,React的state啥的还是要用起来,别破坏别人提供的抽象嘛
所以,最后大致是这样实现的:
const Component: React.FC<ArgType> = () => {
const [ todos, setTodos ] = useState(
() => JSON.parse(localStorage.getItem("todos") ?? "[]" )
)
useEffect( () => {
localStorage.setItem("todos",JSON.stringify(todos)
}, [todos])
return (
//...
)
其中,往todos添加事项的代码不能这么写:
todos.push(todo)
setTodos(todos)
可能是因为React判断两个todos是同一个对象,所以不会执行todos变化时该干的活,这个push等于是绕过了React。能跑的写法是这样的:
let new_todos=[...todos] // Make a copy of todos
new_todos.push(todo)
setTodos(new_todos)
要复制一份,React才会认为他们是两个不同的对象,才会重绘界面。