[React Family Bucket] Through the component design mode, make a simple todo (including source code)

1. Design project structure

Because we implement todo as a functional component, the basic design ideas are:

1. Create a new todo folder in components, create a new index.jsx as a todo component, and put some todo components in this folder

2. Introduce the component in App.jsx import Todo from './components/Todo'and render it in render:

import React, { Component } from 'react';
import Todo from './components/Todo';

export default class APP extends Component {
  render() {
    return (
      <div id="root">
        <Todo />
      </div>
    );
  }
}

Second, split components to achieve static pages

1. We split the todo component into four parts: Header, List, Item and Footer

[External link image transfer failed, the source site may have anti-leech mechanism, it is recommended to save the image and upload it directly (img-oLlmqUDz-1642866764006) (C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\ image-20220122150045635.png)]

2. Create four new folders: Header, List, Item, and Footer in the todo folder, and create new index.jsx and index.css in them respectively.

[External link image transfer failed, the source site may have anti-leech mechanism, it is recommended to save the image and upload it directly (img-o34tDyiN-1642866764012) (C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\ image-20220122153547168.png)]

3. Fill in the static resources into jsx and css respectively

Todo文件夹

/*Todo/index.jsx*/
import React, { Component } from 'react';
import './index.css'
import Footer from './Footer';
import Header from './Header';
import List from './List';

export default class Todo extends Component {
  render() {
    return (
      <div id="root">
        <div className="todo-container">
          <div className="todo-wrap">
            <Header />
            <List />
            <Footer />
          </div>
        </div>
      </div>
    );
  }
}
/*Todo/index.css--公用样式*/
body {
    
    
    background: #fff;
  }
  
  .btn {
    
    
    display: inline-block;
    padding: 4px 12px;
    margin-bottom: 0;
    font-size: 14px;
    line-height: 20px;
    text-align: center;
    vertical-align: middle;
    cursor: pointer;
    box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
    border-radius: 4px;
  }
  
  .btn-danger {
    
    
    color: #fff;
    background-color: #da4f49;
    border: 1px solid #bd362f;
  }
  
  .btn-danger:hover {
    
    
    color: #fff;
    background-color: #bd362f;
  }
  
  .btn:focus {
    
    
    outline: none;
  }
  
  .todo-container {
    
    
    width: 600px;
    margin: 0 auto;
  }
  .todo-container .todo-wrap {
    
    
    padding: 10px;
    border: 1px solid #ddd;
    border-radius: 5px;
  }
  

Header文件夹

/*Header/index.jsx*/

import React, { Component } from 'react';
import './index.css'

export default class Header extends Component {
  render() {
    return (
      <div className="todo-header">
        <input type="text" placeholder="请输入你的任务名称,按回车键确认" />
      </div>
    )
  }
}

/*header/index.css*/
.todo-header input {
    
    
    width: 560px;
    height: 28px;
    font-size: 14px;
    border: 1px solid #ccc;
    border-radius: 4px;
    padding: 4px 7px;
}

.todo-header input:focus {
    
    
    outline: none;
    border-color: rgba(82, 168, 236, 0.8);
    box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6);
}

List文件夹

/*List/index.jsx*/
import React, { Component } from 'react';
import Item from '../Item';
import './index.css'

export default class List extends Component {
  render() {
    return (
      <ul className="todo-main">
        <Item />
      </ul>
    );
  }
}
/*List/index.css*/
.todo-main {
    
    
    margin-left: 0px;
    border: 1px solid #ddd;
    border-radius: 2px;
    padding: 0px;
}

.todo-empty {
    
    
    height: 40px;
    line-height: 40px;
    border: 1px solid #ddd;
    border-radius: 2px;
    padding-left: 5px;
    margin-top: 10px;
}

Item文件夹

/*Item/index.jsx*/
import React, { Component } from 'react';
import './index.css'

export default class Item extends Component {
  render() {
    return (
      <div>
        <li>
          <label>
            <input type="checkbox" />
            <span></span>
          </label>
          <button className="btn btn-danger" style={
   
   { display: 'none' }}>删除</button>
        </li>
      </div>
    );
  }
}

/*Item/index.css*/
li {
    
    
  list-style: none;
  height: 36px;
  line-height: 36px;
  padding: 0 5px;
  border-bottom: 1px solid #ddd;
}

li label {
    
    
  float: left;
  cursor: pointer;
}

li label li input {
    
    
  vertical-align: middle;
  margin-right: 6px;
  position: relative;
  top: -1px;
}

li button {
    
    
  float: right;
  display: none;
  margin-top: 3px;
}

li:before {
    
    
  content: initial;
}

li:last-child {
    
    
  border-bottom: none;
}

Footer文件夹

/*Footer/index.jsx*/
import React, { Component } from 'react';
import './index.css'

export default class Footer extends Component {
  render() {
    return (
      <div className="todo-footer">
        <label>
          <input type="checkbox" />
        </label>
        <span>
          <span>已完成0</span> / 全部2
        </span>
        <button className="btn btn-danger">清除已完成任务</button>
      </div>
    );
  }
}

/*Footer/index.css*/
.todo-footer {
    
    
    height: 40px;
    line-height: 40px;
    padding-left: 6px;
    margin-top: 5px;
}

.todo-footer label {
    
    
    display: inline-block;
    margin-right: 20px;
    cursor: pointer;
}

.todo-footer label input {
    
    
    position: relative;
    top: -1px;
    vertical-align: middle;
    margin-right: 5px;
}

.todo-footer button {
    
    
    float: right;
    margin-top: 5px;
}

[External link image transfer failed, the source site may have anti-leech mechanism, it is recommended to save the image and upload it directly (img-CbURRJfM-1642866764015) (C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\ image-20220122155516577.png)]

3. Function 1_Add task information to the first line

因为涉及到兄弟组件之间的传值,所以我们这里需要通过与父组件的Props传值方法

1. First, set the analog number in the parent component (Todo/index.jsx) and pass it to the child component List

/*Todo/index.jsx*/
import React, { Component } from 'react';
import './index.css'
import Footer from './Footer';
import Header from './Header';
import List from './List';

export default class APP extends Component {
  state = {
    todos: [{
      id: '001',
      name: '吃饭',
      done: true
    }, {
      id: '002',
      name: '睡觉',
      done: true
    }]
  }
  render() {
    const { todos } = this.state
    return (
      <div id="root">
        <div className="todo-container">
          <div className="todo-wrap">
            <Header />
            <List todos={todos} />
            <Footer />
          </div>
        </div>
      </div>
    );
  }
}

2. The List component receives the Props passed from the parent component Todo, and transmits the value to the subcomponent Item of the List for display

/*List/index.jsx*/
import React, { Component } from 'react';
import Item from '../Item';
import './index.css'

export default class List extends Component {
  render() {
    const { todos } = this.props;
    return (
      <ul className="todo-main">
        {
          todos.map((item) => {
            return <Item key={item.id} {...item} />
          })
        }
      </ul>
    );
  }
}

3. The Item component receives the Props from the parent component List and displays it

import React, { Component } from 'react';
import Item from '../Item';
import './index.css'

export default class List extends Component {
  render() {
    const { todos } = this.props;
    return (
      <ul className="todo-main">
        {
          todos.map((item) => {
            return <Item key={item.id} {...item} />
          })
        }
      </ul>
    );
  }
}

[External link image transfer failed, the source site may have anti-leech mechanism, it is recommended to save the image and upload it directly (img-MKw1rR58-1642866764017) (C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\ image-20220122160628490.png)]

4. The parent component Todo obtains the data added in the child component Header through the props transfer function (addTodo)

import React, { Component } from 'react';
import './index.css'
import Footer from './Footer';
import Header from './Header';
import List from './List';

export default class APP extends Component {
  state = {
    todos: [{
      id: '001',
      name: '吃饭',
      done: true
    }, {
      id: '002',
      name: '睡觉',
      done: true
    }]
  }
  //用于添加一个todo,参数为todo对象
  addTodo = (todoObj) => {
    let { todos } = this.state;
    //将输入的内容放到数组第一位
    todos.unshift(todoObj);
    //修改状态
    this.setState(todos)
  }
  render() {
    const { todos } = this.state
    return (
      <div id="root">
        <div className="todo-container">
          <div className="todo-wrap">
            <Header addTodo={this.addTodo} />
            <List todos={todos} />
            <Footer />
          </div>
        </div>
      </div>
    );
  }
}

5. The Header component executes the function passed from the parent component Todo by listening to the "Enter key" to update the status

nanoid的作用是生成唯一的值,主要为了给元素的key进行赋值

import React, { Component } from 'react';
//类似于uuid,生成唯一的id
import { nanoid } from 'nanoid';
import './index.css'

export default class Header extends Component {
  handleKeyUp = (event) => {
    const { keyCode, target } = event;
    if (keyCode === 13) {
      //这里还需要判断空格的多种情况(正则)
      if (target.value !== "") {
        const newTodo = { id: nanoid(), name: target.value, done: false };
        this.props.addTodo(newTodo);
        //清空输入的内容
        target.value = ""
      }
    }
  }
  render() {
    return (
      <div className="todo-header">
        <input type="text" placeholder="请输入你的任务名称,按回车键确认" onKeyUp={this.handleKeyUp} />
      </div>
    )
  }
}

[External link image transfer failed, the source site may have anti-leech mechanism, it is recommended to save the image and upload it directly (img-PzOxbJRp-1642866764019) (D:\desktop\GIF1.gif)]

4. Function 2_ When the mouse moves into the list, it will be highlighted and the "Delete" button will be displayed

此功能主要通过鼠标的移入移出事件进行判断,高亮的功能可以通过css中的:hover实现,本案例使用React中的方法

1. Define the component state

state = { mouseType: false }

2. Define the mouse move in and move out events, true means move in, false means move out; and the backgroundColor and display are rendered correspondingly according to the mouseType value in the state. (This is implemented using the currying method of higher-order functions)

import React, { Component } from 'react';
import './index.css'

export default class Item extends Component {
  state = { mouseType: false }
  //鼠标移入移出事件
  handleMouse = (mouseType) => {
    return () => {
      this.setState({ mouseType })
    }
  }
  render() {
    const { name, done } = this.props
    return (
      <div>
        <li style={
   
   { backgroundColor: this.state.mouseType ? '#ddd' : 'white' }} onMouseEnter={this.handleMouse(true)} onMouseLeave={this.handleMouse(false)}>
          <label>
            <input type="checkbox" defaultChecked={done} />
            <span>{name}</span>
          </label>
          <button className="btn btn-danger" style={
   
   { display: this.state.mouseType ? 'block' : 'none' }}>删除</button>
        </li>
      </div>
    );
  }
}

insert image description here

Five, function 3_ by clicking the checkbox, change the done in the state

1. Because to implement the child component Item to modify the state in the parent component Todo of the parent component List, it is necessary to define the updateTodo function in the parent component Todo and pass it to the Item component through props

/*Todo/index.jsx*/

//用于更新todo,参数为id
  updateTodo = (id, done) => {
    const { todos } = this.state;
    let newTodos = todos.map((item) => {
      if (item.id === id) {
        return { ...item, done }
      } else {
        return item
      }
    })
    this.setState({ todos: newTodos })
  }
<List todos={todos} updateTodo={this.updateTodo} />

List/index.jsx

<Item key={item.id} {...item} updateTodo={updateTodo} />

2. In the Item component, add an onChange event to the checkbox, and pass the id of the todo corresponding to the currently selected checkbox as a parameter to handleCheck

<input type="checkbox" defaultChecked={done} onChange={this.handleCheck(id)} />

3. In the handleCheck function, execute the id and the checked of the current checkbox as the parameters of updateTodo

//勾选或者取消勾选
handleCheck = (id) => {
  return (event) => {
    this.props.updateTodo(id, event.target.checked)
  }
}

4. Introduce the prop-types library to limit the props of each component (see how to use: npm.com prop-types )

  /*以Item/index.jsx为例*/
  //对props进行限制
  static propTypes = {
    updateTodo: PropTypes.func.isRequired,
    done: PropTypes.bool.isRequired
  }

insert image description here

Six, function 4_ delete a todo

1. The logic is the same as the way to modify done. First, in the Item component, bind onClick to the delete button

<button onClick={this.handleDel(id)} className="btn btn-danger" style={
   
   { display: this.state.mouseType ? 'block' : 'none' }}>删除</button>

2. Define the handleDel function and execute the deleteTodo function passed from the Todo component

  //删除一个todo
  handleDel = (id) => {
    return () => {
      this.props.deleteTodo(id)
    }
  }

3. Define the deleteTodo function in the Todo component, pass it to the subcomponent List, and then pass it to the subcomponent Item through the List component

Todo/index.jsx

//删除指定todo对象
  deleteTodo = (id) => {
    const { todos } = this.state;
    const newTodos = todos.filter(item => {
      return item.id !== id
    })
    this.setState({ todos: newTodos })
  }
<List todos={todos} updateTodo={this.updateTodo} deleteTodo={this.deleteTodo} />

List/index.jsx

<Item key={item.id} {...item} updateTodo={updateTodo} deleteTodo={deleteTodo} />

7. Function 5_ Realize the function of selecting all and clearing at the bottom

1. First realize the statistics of the completed number and all the numbers

Footer/index.jsx

const { todos } = this.props
//已完成数
const doneCount = todos.reduce((pre, current) => {
  return current.done === true ? ++pre : pre
}, 0)
<span>
  <span>已完成{doneCount}</span> / 全部{todos.length}
</span>

2. Implement the function of selecting all

Footer/index.jsx

<input type="checkbox" checked={doneCount === todos.length && todos.length !== 0 ? true : false} onChange={this.handleCheckAll} />
//全选
handleCheckAll = (event) => {
	this.props.checkAllTodo(event.target.checked)
}

3. Because you want to change the data state, you need to define a function in the parent component Todo and pass it through props

Todo/index.jsx

//全选
checkAllTodo = (done) => {
    const { todos } = this.state;
    let newTodos = todos.map(item => {
      return { ...item, done: done }
    })
    this.setState({ todos: newTodos })
}
<Footer todos={todos} checkAllTodo={this.checkAllTodo} />

4. Realize the function of clearing completed

Footer/index.jsx

<button className="btn btn-danger" onClick={this.handleClear}>清除已完成任务</button>
//清除已完成任务
handleClear = () => {
	this.props.clearAllDone()
}

5. Because you want to change the data state, you need to define a function in the parent component Todo and pass it through props

Todo/index.jsx

//清除所有已完成
clearAllDone = () => {
    const { todos } = this.state;
    const newTodos = todos.filter(item => {
      return item.done === false
    })
    this.setState({ todos: newTodos })
}
<Footer todos={todos} checkAllTodo={this.checkAllTodo} clearAllDone={this.clearAllDone} />

insert image description here

8. Case summary

1. Split components and implement static components. Note: the writing of className and style

2. Dynamic initialization list, how to determine which component's state to put data in

​ A component uses: put it in its own state

​ Some components use: put them in their common parent component state (officially called state promotion )

3. Regarding the communication between father and son:

​ (1) [Parent component] passes data to [child component] through props

​ (2) [Child component] passes data to [parent component]: pass through props, requiring [parent component] to pass a function to [child component] in advance

4. Pay attention to the difference between defaultChecked and Checked, similar to defaultValue and Value

5. Where is the state, where is the method of operating the state

Source address: https://gitee.com/daiwanghao/react-family-bucket.git

For the content of todo made by React above, please pay attention to the column " React Family Bucket ".
I will share the common problems in my usual projects and the knowledge of the written test and interview with you on CSDN, and make progress together. Come on.

Guess you like

Origin blog.csdn.net/weixin_46318413/article/details/122646051