react实现todomvc

在学习了 react 的父子组件通讯后,尝试着做了一个大致如下的 todomvc 案例:

这里关于 todomvc 的基本结构及样式就不去过多的描述了

我们可以去 github todumvc 样板

上进行下载其基本的结构

大致结构如下:

index.js

/**
 * 1. 导入react和react-dom
 * 2. 创建 react 元素
 * 3. 把 react 元素渲染到页面
 */
 import React from 'react';
 import ReactDom from 'react-dom/client';
 import { Component } from 'react';
 import './styles/base.css';
 import './styles/index.css'

 class App extends Component {
     render() {
         return (
            <section className="todoapp">
			<header className="header">
				<h1>todos</h1>
				<input className="new-todo" placeholder="What needs to be done?" autoFocus />
			</header>
			
			<section className="main">
				<input id="toggle-all" className="toggle-all" type="checkbox" />
				<label htmlFor="toggle-all">Mark all as complete</label>
				<ul className="todo-list">
					
					<li className="completed">
						<div className="view">
							<input className="toggle" type="checkbox" checked />
							<label>Taste JavaScript</label>
							<button className="destroy"></button>
						</div>
						<input className="edit" value="Create a TodoMVC template" />
					</li>
					<li>
						<div className="view">
							<input className="toggle" type="checkbox" />
							<label>Buy a unicorn</label>
							<button className="destroy"></button>
						</div>
						<input className="edit" value="Rule the web" />
					</li>
				</ul>
			</section>
			
			<footer className="footer">
				
				<span className="todo-count"><strong>0</strong> item left</span>
				
				<ul className="filters">
					<li>
						<a className="selected" href="#/">All</a>
					</li>
					<li>
						<a href="#/active">Active</a>
					</li>
					<li>
						<a href="#/completed">Completed</a>
					</li>
				</ul>
				
				<button className="clear-completed">Clear completed</button>
			</footer>
		</section>
         )
     }
 }


// 幽灵节点:节点不会渲染任何的内容,跟 vue 里面的 template 标签一样
const element = (
    <React.Fragment>
        <App></App>
    </React.Fragment>
);
  
// 参数1:渲染的 react 元素即虚拟 DOM
// 参数2:需要渲染到哪个容器中
const root = ReactDom.createRoot(document.getElementById('root'));
root.render(element);

接着我们需要做的就是将其进行组件化拆分,大致分为如下三个部分:

在 src 目录下新建 components 文件夹,专门用来存放组件

将 src/index.js 文件进行如上图拆分

components/TodoHeader.js

import React,{ Component } from 'react';

export default class TodoHeader extends Component {
    render() {
        return (
            <header className="header">
				<h1>todos</h1>
				<input className="new-todo" placeholder="What needs to be done?" autoFocus />
			</header>
        )
    }
}

components/TodoMain.js

import React,{ Component } from 'react';

export default class TodoHeader extends Component {
    render() {
        return (
            <section className="main">
				<input id="toggle-all" className="toggle-all" type="checkbox" />
				<label htmlFor="toggle-all">Mark all as complete</label>
				<ul className="todo-list">
					
					<li className="completed">
						<div className="view">
							<input className="toggle" type="checkbox" checked />
							<label>Taste JavaScript</label>
							<button className="destroy"></button>
						</div>
						<input className="edit" value="Create a TodoMVC template" />
					</li>
					<li>
						<div className="view">
							<input className="toggle" type="checkbox" />
							<label>Buy a unicorn</label>
							<button className="destroy"></button>
						</div>
						<input className="edit" value="Rule the web" />
					</li>
				</ul>
			</section>
        )
    }
}

 components/TodoFooter.js

import React,{ Component } from 'react';

export default class TodoHeader extends Component {
    render() {
        return (
            <footer className="footer">
				<span className="todo-count"><strong>0</strong> item left</span>
				<ul className="filters">
					<li>
						<a className="selected" href="#/">All</a>
					</li>
					<li>
						<a href="#/active">Active</a>
					</li>
					<li>
						<a href="#/completed">Completed</a>
					</li>
				</ul>
				<button className="clear-completed">Clear completed</button>
			</footer>
        )
    }
}

index.js

/**
 * 1. 导入react和react-dom
 * 2. 创建 react 元素
 * 3. 把 react 元素渲染到页面
 */
 import React from 'react';
 import ReactDom from 'react-dom/client';
 import { Component } from 'react';
 import './styles/base.css';
 import './styles/index.css'
 import TodoHeader from './components/TodoHeader';
 import TodoMain from './components/TodoMain';
 import TodoFooter from './components/TodoFooter';

 class App extends Component {
     render() {
         return (
            <section className="todoapp">
            <TodoHeader></TodoHeader>
			<TodoMain></TodoMain>
			<TodoFooter></TodoFooter>
		</section>
         )
     }
 }


// 幽灵节点:节点不会渲染任何的内容,跟 vue 里面的 template 标签一样
const element = (
    <React.Fragment>
        <App></App>
    </React.Fragment>
);
  
// 参数1:渲染的 react 元素即虚拟 DOM
// 参数2:需要渲染到哪个容器中
const root = ReactDom.createRoot(document.getElementById('root'));
root.render(element);

在完成了上面的步骤之后,从这里开始,正式进行案例的开发

首先,我们需要实现的就是 任务列表渲染功能

这里需要先在父组件(index.js)里准备数据,然后传递到子组件(TodoMain.js)里进行展示,就需要用到组件通讯里的父传子:

  1. 父组件提供要传递的state数据

  2. 给子组件标签添加属性,值为 state 中的数据

  3. 子组件中通过 props 接收父组件中传递的数据

注意:接收数据:函数组件通过参数props接收数据,类组件通过this.props接收数据

 TodoMain.js

import React,{ Component } from 'react';

export default class TodoHeader extends Component {
    render() {
        const { list } = this.props;
        return (
            <section className="main">
				<input id="toggle-all" className="toggle-all" type="checkbox" />
				<label htmlFor="toggle-all">Mark all as complete</label>
				<ul className="todo-list">
					{
                        list.map(item => {
                            return (
                                <li className={item.done ? 'completed' : ''} key={item.id}>
						        <div className="view">
							        <input className="toggle" type="checkbox" checked={item.done} />
							        <label>{item.name}</label>
							        <button className="destroy"></button>
						        </div>
						        <input className="edit" value="Create a TodoMVC template" />
					            </li>
                            )
                        })
                    }
				</ul>
			</section>
        )
    }
}

index.js

/**
 * 1. 导入react和react-dom
 * 2. 创建 react 元素
 * 3. 把 react 元素渲染到页面
 */
 import React from 'react';
 import ReactDom from 'react-dom/client';
 import { Component } from 'react';
 import './styles/base.css';
 import './styles/index.css'
 import TodoHeader from './components/TodoHeader';
 import TodoMain from './components/TodoMain';
 import TodoFooter from './components/TodoFooter';

 class App extends Component {
     state = {
         list: [
             {
                 id: 1,
                 name: 'JavaScript',
                 done: false
             },
             {
                id: 2,
                name: 'Vue',
                done: false
            },
            {
                id: 3,
                name: 'react',
                done: true
            }
         ]
     }
     render() {
         const { list } = this.state;
        //  console.log(list);
         return (
            <section className="todoapp">
            <TodoHeader></TodoHeader>
			<TodoMain list={list}></TodoMain>
			<TodoFooter></TodoFooter>
		</section>
         )
     }
 }


// 幽灵节点:节点不会渲染任何的内容,跟 vue 里面的 template 标签一样
const element = (
    <React.Fragment>
        <App></App>
    </React.Fragment>
);
  
// 参数1:渲染的 react 元素即虚拟 DOM
// 参数2:需要渲染到哪个容器中
const root = ReactDom.createRoot(document.getElementById('root'));
root.render(element);

其次,我们需要实现的就是 删除任务功能

 当点击删除按钮时,实现删除

这就需要给每个 li 注册点击事件,实现删除

但是需要注意的就是 li 的渲染是在 TodoMain.js 里的,而数据是在 index.js(父组件)里,因此就需要用到组件通信里的子传父通过当前点击的 id 进行删除

思路:利用回调函数,父组件提供回调,子组件调用,将要传递的数据作为回调函数的参数。

  1. 父组件提供一个回调函数(用于接收数据)

  2. 将该函数作为属性的值,传递给子组件

  3. 子组件通过 props 调用回调函数

  4. 将子组件的数据作为参数传递给回调函数

index.js

/**
 * 1. 导入react和react-dom
 * 2. 创建 react 元素
 * 3. 把 react 元素渲染到页面
 */
 ...

 class App extends Component {
     state = {
         ...
     }
     render() {
         ...
         <TodoMain delTodoById = {this.delTodoById}></TodoMain>
         ...
     }
     delTodoById = (id) => {
         this.setState({
             list: this.state.list.filter(item => item.id !== id)
         })
     }
 }


...

TodoMain.js

import React,{ Component } from 'react';

export default class TodoHeader extends Component {
    render() {
        const { list } = this.props;
        return (
            <section className="main">
				...
				<ul className="todo-list">
					{
                        list.map(item => {
                            return (
                                ...
							        <button className="destroy" onClick={() => {this.del(item.id)}}></button>
						        ...
					            </li>
                            )
                        })
                    }
				</ul>
			</section>
        )
    }
    // 删除
    del = (id) => {
        // console.log(id);
        this.props.delTodoById(id);
    }
}

然后,我们需要实现的就是 任务状态修改功能

这个主要就是改变 checked  的一个状态即可

但是状态的改变是定义在index.js(父组件)的state里的--done

因此只能通过组件通讯中的子传父的形式进行传递,将当前点击的那个id,传递给父组件,在父组件里找到并修改对应id的done值

index.js

/**
 * 1. 导入react和react-dom
 * 2. 创建 react 元素
 * 3. 把 react 元素渲染到页面
 */
 ...
 class App extends Component {
     state = {
         list: [
             {
                 id: 1,
                 name: 'JavaScript',
                 done: false
             },
             {
                id: 2,
                name: 'Vue',
                done: false
            },
            {
                id: 3,
                name: 'react',
                done: true
            }
         ]
     }
     render() {
         const { list } = this.state;
         return (
            ...
			<TodoMain list={list} delTodoById = {this.delTodoById} updateDone = {this.updateDone}></TodoMain>
			...
         )
     }
     // 修改任务状态
     updateDone = (id) => {
         this.setState({
             list: this.state.list.map(item => {
                 if(item.id === id){
                    return {
                        ...item,
                        done: !item.done
                    };
                 }else {
                     return item;
                 }
             })
         })
     }
 }


// 幽灵节点:节点不会渲染任何的内容,跟 vue 里面的 template 标签一样
const element = (
    <React.Fragment>
        <App></App>
    </React.Fragment>
);
  
// 参数1:渲染的 react 元素即虚拟 DOM
// 参数2:需要渲染到哪个容器中
const root = ReactDom.createRoot(document.getElementById('root'));
root.render(element);

TodoMain.js

import React,{ Component } from 'react';

export default class TodoHeader extends Component {
    render() {
        const { list } = this.props;
        return (
            <section className="main">
				...
				<ul className="todo-list">
					{
                        list.map(item => {
                            return (
                                ...
							        <input className="toggle" type="checkbox" checked={item.done} onChange={() => this.updateDone(item.id)} />
							        ...
                            )
                        })
                    }
				</ul>
			</section>
        )
    }
    // 修改任务状态
    updateDone = (id) => {
        this.props.updateDone(id);
    }
}

接着,我们需要实现的是 添加任务 功能

对于这个功能的实现主要利用的就是键盘的回车事件

在 TodoHeader.js 中通过 value 拿到值

但是数据的增加是在 index.js 里的,也就是最终需要把数据添加到 index.js(父组件)的 list 里,因此就需要用到组件通讯中的子传父

import React,{ Component } from 'react';

export default class TodoHeader extends Component {
    state = {
        name: ''
    }
    render() {
        return (
            <header className="header">
				<h1>todos</h1>
				<input className="new-todo" 
                placeholder="What needs to be done?" 
                autoFocus
                value={this.state.name}
                onChange={this.addTas}
                onKeyUp={this.addTo}
                />
			</header>
        )
    }
    // 添加任务
    addTas = (e) => {
        this.setState({
            name: e.target.value
        })
    }
    addTo = (e) => {
        // 按下回车键后才触发
        if(e.keyCode !== 13){
            return;
        }
        if(!this.state.name.trim()){
            return alert('任务不能为空!');
        }
        // 添加 todo
        this.props.addTo(this.state.name);
        // 清空 name
        this.setState({
            name: ''
        })
    }
}

index.js

/**
 * 1. 导入react和react-dom
 * 2. 创建 react 元素
 * 3. 把 react 元素渲染到页面
 */
...

 class App extends Component {
     state = {
         list: [
             {
                 id: 1,
                 name: 'JavaScript',
                 done: false
             },
             {
                id: 2,
                name: 'Vue',
                done: false
            },
            {
                id: 3,
                name: 'react',
                done: true
            }
         ]
     }
     render() {
         const { list } = this.state;
         return (
           ...
            <TodoHeader addTo={this.addTo}></TodoHeader>
			...
         )
     }
     ...
     // 添加任务
     addTo = (name) => {
        //  console.log(name);
        this.setState({
            list: [{
                id: Date.now(),
                name: name,
                done: false
            } ,
            ...this.state.list]
        })
     }
 }


// 幽灵节点:节点不会渲染任何的内容,跟 vue 里面的 template 标签一样
const element = (
    <React.Fragment>
        <App></App>
    </React.Fragment>
);
  
// 参数1:渲染的 react 元素即虚拟 DOM
// 参数2:需要渲染到哪个容器中
const root = ReactDom.createRoot(document.getElementById('root'));
root.render(element);

紧接着,我们需要实现的就是 双击显示 修改框

为了准确找到当前双击的这个id ,在 TodoMain.js 里通过 state 保存了一个当前双击的那个任务的 id 的状态(currentId),将当前双击的这个 id 传进去

当双击 lable 标签之后,拿到其对应的 id,并且保存到 currentId 里

在 li 里去判断 item.id 是否等于 currentId 但是这里需要同时控制两个 类名,因此我们需要通过 npm install classnames 下载 classnames

TodoMain.js

import React,{ Component } from 'react';
import classnames from 'classnames';

export default class TodoHeader extends Component {
    state = {
        // 存放当前双击的id
        currentId: ''
    }
    render() {
        const { list } = this.props;
        return (
            <section className="main">
				...
					{
                        list.map(item => {
                            return (
                                <li className={classnames({
                                    completed: item.done,
                                    editing: item.id === this.state.currentId
                                })} key={item.id}>
						            ...
							        <label onDoubleClick={() => this.showEdit(item.id)}>{item.name}</label>
							       ...
						        <input className="edit" />
					            </li>
                            )
                        })
                    }
				</ul>
			</section>
        )
    }
    ...
    // 双击显示修改框
    showEdit = (id) => {
        // console.log(id);
        this.setState({
            currentId: id
        })
    }
}

实现双击回显任务名称

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小白小白从不日白

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值