使用React实现Todolist
创建React项目
由于是第一次使用React做项目,所以就从最开始进行记录
- npm install react
- npm install react-dom
- npm create vite@latest
- project name: todolist
- Select a frameworl: React
- Select a variant: TypeScript + SWC
- cd ./todolist
- npm install
- npm run dev
- 打开http://127.0.0.1:5173/
一些细节
javaScript中通常onchange只会在模糊时触发,或者像当停止改变某些东西的时候(在输入时使用);onInput一般在每次更改输入中的值时使用
App.jsx
最开始在App.jsx中实现整个todolist案例,随着项目代码的增加,将项目的组件进行封装,分别包括NewItemForm.jsx,TodoList.jsx,TodoItem.jsx
封装组件
1 App.jsx
import './styles.css'
import { useState, useEffect } from 'react'
import { NewTodoForm } from './NewTodoForm.jsx'
import { TodoList } from './TodoList.jsx'
export default function App() {
const [todos, setTodos] = useState(() => {
const localValue = localStorage.getItem('ITEMS')
if (localValue == null) return []
return JSON.parse(localValue)
})
useEffect(() => {
localStorage.setItem('ITEMS', JSON.stringify(todos))
}, [todos])
function addTodo(title) {
setTodos((currentTodos) => {
return [...currentTodos, { id: crypto.randomUUID(), title, completed: false }]
})
}
function toggleTodo(id, completed) {
setTodos((currentTodos) => {
return currentTodos.map((todo) => {
if (todo.id === id) {
// todo.completed = completed
return { ...todo, completed }
}
return todo
})
})
}
function deleteTodo(id) {
setTodos((currentTodos) => {
return currentTodos.filter((todo) => todo.id !== id)
})
}
return (
<>
<div className="todo">
<NewTodoForm onSubmit={addTodo} />
<h2 className="header">Todo List</h2>
<TodoList todos={todos} toggleTodo={toggleTodo} deleteTodo={deleteTodo} />
</div>
</>
)
}
2 NewTodoForm.jsx
该.jsx文件主要是展现Form表单及其功能
import { useState } from 'react'
export function NewTodoForm({ onSubmit }) {
const [newItem, setNewItem] = useState('')
function handleSubmit(e) {
e.preventDefault()
if (newItem === '') return
onSubmit(newItem)
setNewItem('')
}
return (
<form onSubmit={handleSubmit} className="new-item-form">
<div className="form-row">
<label htmlFor="item" className="new-item-label">
New Item
</label>
</div>
<div className="add-box">
<input
value={newItem}
onChange={(e) => {
setNewItem(e.target.value)
}}
type="text"
id="item"
/>
<button className="btn">Add</button>
</div>
</form>
)
}
3 TodoList.jsx
该文件封装的是Todo List中的任务列表,同时将li任务列表还进行了封装--TodoItem.jsx
import { TodoItem } from './TodoItem.jsx'
export function TodoList({ todos, toggleTodo, deleteTodo }) {
return (
<ul className="list">
{todos.length === 0 && 'No Todos'}
{todos.map((todo) => {
// return <TodoItem {...todos} id={todo.id} completed={todo.completed} title={todo.title} key={todo.id}></TodoItem>
return <TodoItem {...todo} key={todo.id} toggleTodo={toggleTodo} deleteTodo={deleteTodo}></TodoItem>
})}
</ul>
)
}
4 TodoItem.jsx
该.jsx文件主要实现每个任务的展示及其操作,通过父文件传递过来的函数进行选中、删除操作
export function TodoItem({ completed, title, id, toggleTodo, deleteTodo }) {
return (
<li key={id}>
<label>
<input
type="checkbox"
checked={completed}
onChange={(e) => {
toggleTodo(id, e.target.checked)
}}
/>
{title}
</label>
<button className="btn btn-danger" onClick={() => deleteTodo(id)}>
删除
</button>
{/* <button className="btn btn-danger">Delete</button> */}
</li>
)
}
回顾使用Vue实现Todolist
todolist的页面:
清除已完成任务后:
添加任务:
删除全部任务:
该任务基于vue3.0实现,通过路由实现未完成、已完成、全部任务的不同页面及其跳转; 在全部任务页面使用计算属性动态显示已完成任务数量、全部任务数量,以及通过计算属性获取vuex中定义的todolist的数据;
以下展示部分主要代码:
// 全部任务
<template>
<div>
<div>
<h2>全部任务</h2>
</div>
<div>
<nav-header @add="add"></nav-header>
<nav-main :list="list" @del="del"></nav-main>
<nav-footer :list="list" @clear="clear" @clearall="clearall"></nav-footer>
</div>
</div>
</template>
<script>
import { defineComponent, ref, computed } from 'vue'
import NavMain from '../components/navMain/navMain.vue'
import NavHeader from '../components/navHeader/navHeader.vue'
import NavFooter from '../components/navFooter/navFooter.vue'
import { useStore } from 'vuex'
export default defineComponent({
name: 'Home',
props: {},
components: {
NavHeader,
NavMain,
NavFooter,
},
setup() {
let store = useStore()
let list = computed(() => {
return store.state.list
})
let value = ref('')
// 添加任务
let add = (val) => {
value.value = val
// 先判断是否是重复任务
let flag = true
list.value.map((item) => {
if (item.title === value.value) {
// 有重复任务
flag = false
alert('任务已存在')
}
})
// 没有重复的任务
if (flag) {
// 调用mutaiton
store.commit('addTodo', {
title: value.value,
complete: false,
})
}
}
let del = (val) => {
// 调用删除的mutation
store.commit('delTodo', val)
}
let clear = (val) => {
store.commit('clear', val)
}
let clearall = (val) => {
store.commit('clearall', val)
}
return {
add,
del,
clear,
clearall,
value,
list,
}
},
})
</script>
<style scoped lang="scss"></style>
在全部任务页面定义三个子组件:navHeader,navMain,navFooter,通过这样的方式囊括vue3.0的知识点,包括:父子组件之间传参(添加任务、删除已完成任务、按需删除任务)
// navHeader.vue
<template>
<div>
<input placeholder="请输入任务名称" v-model="value" />
<button @click="enter">添加</button>
</div>
</template>
<script>
import { defineComponent, ref } from 'vue'
export default defineComponent({
name: 'navheader',
props: {},
components: {},
setup(props, ctx) {
let value = ref('')
let enter = () => {
ctx.emit('add', value.value)
// console.log(value.value)
value.value = ''
}
return {
value,
enter,
}
},
})
</script>
<style lang="scss" scoped>
input {
margin: 10px;
}
</style>
// navMain.vue
<template>
<div v-if="list.length > 0">
<div v-for="(item, index) in list" :key="index">
<div class="item">
<input type="checkbox" v-model="item.complete" />
{{ item.title }}
<button class="del" @click="del(item, index)">删除</button>
</div>
</div>
</div>
<div v-else>暂无任务</div>
</template>
<script>
import { defineComponent, ref } from 'vue'
export default defineComponent({
name: 'navmain',
props: {
list: {
type: Array,
required: true,
},
},
emits: ['del'],
components: {},
setup(props, ctx) {
// 删除任务
let del = (item, index) => {
ctx.emit('del', index)
}
return {
del,
}
},
})
</script>
<style lang="scss" scoped>
.item {
height: 35px;
line-height: 35px;
position: relative;
cursor: pointer;
width: 240px;
margin: auto;
.del {
position: absolute;
right: 10px;
top: 6px;
display: none;
z-index: 9999;
}
&:hover {
background-color: #ddd;
button {
display: block;
}
}
}
</style>
// navFooter.vue
<template>
<div class="navfooter-container">
<div>已完成{{ isComplete }} / 全部{{ list.length }}</div>
<div v-if="isComplete > 0" class="del_btn">
<button class="del" @click="clear">清除已完成</button>
<button class="delall" @click="clearall">清楚全部</button>
</div>
</div>
</template>
<script>
import { defineComponent, ref, computed } from 'vue'
export default defineComponent({
name: 'navfooter',
props: {
list: {
type: Array,
required: true,
},
},
components: {},
setup(props, ctx) {
let isComplete = computed(() => {
// 过滤已完成的任务
let arr = props.list.filter((item) => {
return item.complete
})
return arr.length
})
// 清除已完成
let clear = () => {
// 过滤未完成的任务
let arr = props.list.filter((item) => {
return item.complete === false
})
ctx.emit('clear', arr)
}
// 清除全部
let clearall = () => {
// 过滤未完成的任务
let arr = props.list
console.log(arr)
ctx.emit('clearall', arr)
}
return {
isComplete,
clear,
clearall,
}
},
})
</script>
<style scoped lang="scss">
.navfooter-container {
display: flex;
width: 500px;
margin: 0 auto;
justify-content: center;
align-items: center;
.del_btn {
margin-left: 50px;
}
}
</style>
// store.js
import { createStore } from 'vuex'
export default createStore({
// state定义所需要的状态
state: {
list: [
{
title: '吃饭',
complete: false,
},
{
title: '学习前端',
complete: false,
},
{
title: '行测回顾',
complete: false,
},
],
},
getters: {},
// mutations是用于同步修改state,都是方法
// 第一个参数state 第二个参数是需要修改的值
mutations: {
setName(state, payload) {
// state.name = payload
state.name = payload
},
addTodo(state, payload) {
state.list.push(payload)
},
// 删除任务 splice(下标, 个数)方法
delTodo(state, payload) {
state.list.splice(payload, 1)
},
// 清除已完成
clear(state, payload) {
// 把过滤之后的数组传进来
state.list = payload
},
clearall(state, payload) {
// 把过滤之后的数组传进来
state.list = []
},
},
// 异步提交mutation
// 第一个参数是store,第二个参数是修改的值
actions: {
// asyncSetName(store, params) {
// setTimeout(() => {
// // commit是提交mutation,调用mutation的方法
// store.commit('setName', params)
// }, 2000)
// },
},
// 模块化
modules: {},
})
// router.js
import { createRouter, createWebHistory } from 'vue-router'
import HomeView from '../views/HomeView.vue'
// 路由的配置数组
// path: 路由路径 必须以/开头
// component: 对应的路由组件
// name: 路由名字
const routes = [
{
path: '/',
name: 'start',
},
{
path: '/start',
name: 'start',
component: () => import('../views/StartView.vue'),
},
{
path: '/home',
name: 'home',
component: HomeView,
},
{
path: '/about',
name: 'about',
component: () => import('../views/AboutView.vue'),
},
]
const router = createRouter({
history: createWebHistory(process.env.BASE_URL),
routes,
})
export default router
总结
在通过使用Vue和React分别实现Todolist案例的过程中,我更加深刻理解了Vue以及React的区别和优缺点。
Vue和React的区别:
-
React的思路是HTML in JavaScript,通过javascript生成HTML,所以设计的jsx语法,还有通过JS来操作CSS等;Vue则是把HTML、CSS、JavaScript组合到一起,用各自的处理方式,Vue有单文件组件,可以把HTML、CSS、JS写到一个文件中。可以了解到,Vue更适合从三组件过渡到框架的使用,但是React更注重对JavaScript的使用;
-
React的生命周期与Vue的生命周期不同:
-
Diff算法不同:Diff算法是一种对比算法,主要是对比旧的虚拟DOM和新的虚拟DOmain,找出发生更改的节点,只更新更改的节点,而不更新未发生变化的节点,从而准确的更新DOM,减少操作真是DOM的次数,提高性能。
- 1、vue节点对比,如果节点元素类型相同,但className不同,认为是不同类型的元素,会进行删除重建,但react则会认为是同类型的节点,只会修改节点属性。
- 2、vue的列表比对采用的是首尾指针法,而react采用的是从左到右依次比对的方式,当一个集合只是把最后一个节点移动到了第一个,react会把前面的节点依次移动,而vue只会把最后一个节点移动到最后一个,从这点上来说vue的对比方式更加高效。
-
响应式原理不同
- React主要是通过setState()方法更新状态,状态更新之后,组件也会重新渲染
- Vue会遍历data数据对象,使用Object.definedProperty()将每个属性都转换为getter和setter,每个Vue组件实例都有一个对应的watcher实例,在组件初次渲染的时候会记录组件用到了哪些数据。当数据发生改变的时候,会触发setter方法,并通知所有依赖这个数据的watcher实例调用update方法去触发组件的compile渲染方法,进行渲染数据。
-
封装程度不同
- vue封装成都更高,内置多个指令和数据双向绑定,react封装程度比较低,适合扩展