每天学点React - 通用功能界面的组件编码流程(二)

23 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第21 天,点击查看活动详情

动态组件

App

由于HeaderBodyFooter等组件为兄弟组件,相互间无法进行参数传递,我们需要通过他们父组件App来实现组件间的数据传递。由于state是定义在App组件中,那么我们针对该状态的操作函数都在该组件中进行实现。

class App extends Component {
    state = {
        todos: [
            { id: 1, name: 'eat', done: true },
            { id: 2, name: 'sleep', done: false },
            { id: 3, name: 'code', done: false },
            { id: 4, name: 'juejin', done: true },
        ]
    }

    // 添加任务
    addTodo = (todo) => {
        const { todos } = this.state
        this.setState({ todos: [todo, ...todos] })
    }

    // 更改todo状态
    updateTodo = (id, done) => {
        const { todos } = this.state
        this.setState({
            todos: todos.map(todo => {
                if (todo.id === id) {
                    return { ...todo, done }
                } else {
                    return todo
                }
            })
        })
    }

    // 批量更改todo状态
    updateTodos = (done) => {
        const { todos } = this.state
        this.setState({
            todos: todos.map(todo => {
                return {...todo, done}
            })
        })
    }


    // 删除todo
    deleteTodo = (id) => {
        const { todos } = this.state
        this.setState({
            todos: todos.filter(todo => todo.id !== id)
        })
    }

    // 删除所有已完成todo
    deleteAllByDone = () => {
        const {todos} = this.state
        this.setState({todos: todos.filter(todo => !todo.done)})
    }

    render() {
        const { todos } = this.state
        return (
            <div id="root">
                <div className="todo-container">
                    <div className="todo-wrap">
                        <Header addTodo={this.addTodo} />
                        <Body todos={todos} updateTodo={this.updateTodo} deleteTodo={this.deleteTodo} />
                        <Footer todos={todos} updateTodos={this.updateTodos} deleteAllByDone={this.deleteAllByDone}/>
                    </div>
                </div>
            </div>
        )
    }
}

Header

具体实现功能:

  1. input输入框添加按键弹起回调事件,获取到输入的值;
  2. 通过keyCode判断当用户触发了回车键,则将当前输入框内的值传递给Todo列表。
export default class Header extends Component {

  // 对接收的参数类型,必要性进行限制
  static propTypes = {
    addTodo: PropTypes.func.isRequired
  }

  // 按钮弹起事件
  handleKeyUp = (event) => {
    // 得到按键Code和目标值
    const {keyCode, target} = event

    if(target.value.trim() === ''){
      alert('输入内容不鞥为空!')
      return
    }

    // 判断是否回车
    if(keyCode === 13){
      // 调用父组件的添加任务函数,将参数传递给父组件
      this.props.addTodo({
        id: nanoid(),
        name: target.value,
        done: false
      })
      target.value = ''
    }
  }

  render() {
    return (
      <div className="todo-header">
        <input onKeyUp={this.handleKeyUp} type="text" placeholder="请输入任务名称,按回车键确认" />
      </div>
    )
  }
}

image.png

image.png

Body

具体实现功能:

  1. 获取到祖父件中的Todo列表,将其渲染到页面中。
  2. 将父组件都函数传递给Item组件,实现具体功能。
export default class Body extends Component {

  // 对接收的参数类型,必要性进行限制
  static propTypes = {
    todos: PropTypes.array.isRequired,
    updateTodo: PropTypes.func.isRequired,
    deleteTodo: PropTypes.func.isRequired
  }

  render() {
    const { todos, updateTodo, deleteTodo } = this.props
    return (
      <ul className="todo-main">
        {
          todos.map(todo => {
            return <Item {...todo} key={todo.id} updateTodo={updateTodo} deleteTodo={deleteTodo}/>
          })
        }
      </ul>
    )
  }
}

Item

具体实现功能:

  1. todo解析渲染,把对应任务展示出来;
  2. 当鼠标移入时给其添加一个灰色的背景色,同时显示删除按钮,在鼠标移出时恢复;
  3. 在我们勾选或者取消勾选的时候,需要更新当前tododone值;
  4. 点击删除按钮提示是否删除,确定删除则删除当前行todo
export default class Item extends Component {

  state = ({ mouse: false })

  // 鼠标移动状态控制
  handlerMouse = (flag) => {
    return () => {
      this.setState({ mouse: flag })
    }
  }

  // 复选框回调
  handleCheck = (id) => {
    return (event) => {
      this.props.updateTodo(id, event.target.checked)
    }
  }

  // 删除按钮回调
  handleClick = (id) => {
    // confirm不能直接写,需要使用window来调用
    if(window.confirm('确认删除该任务吗?')){
      this.props.deleteTodo(id)
    }
  }

  render() {
    const { id, name, done } = this.props
    const { mouse } = this.state
    return (
      <li style={{ backgroundColor: mouse ? '#ddd' : 'white' }} onMouseEnter={this.handlerMouse(true)} onMouseLeave={this.handlerMouse(false)} >
        <label>
          <input onChange={this.handleCheck(id)} type="checkbox" checked={done} />
          <span>{name}</span>
        </label>
        <button className="btn btn-danger" style={{ display: mouse ? 'block' : 'none' }} onClick={() => this.handleClick(id)}>删除</button>
      </li>
    )
  }
}

Footer

具体实现功能:

  1. 当所有todo被选中时,全选按钮需要勾上;
  2. 点击全选/全部取消,上方的todo勾选框也要跟着变化;
  3. 点击删除全部已完成按钮,需要清除勾选的todo
export default class Footer extends Component {

  // 全选按钮回调
  handleCheck = (event) => {
    this.props.updateTodos(event.target.checked)
  }

  // 全部删除按钮回调
  handlerClick = () => {
    this.props.deleteAllByDone()
  }

  render() {
    const { todos } = this.props
    // 计算已完成数量
    const doneCount = todos.reduce((pre, todo) => { return todo.done ? pre + 1 : pre }, 0)
    const total = todos.length
    return (
      <div className="todo-footer">
        <label>
          <input type="checkbox" onChange={this.handleCheck} checked={doneCount === total} />
        </label>
        <span>
          <span>已完成{doneCount}</span> / 全部{total}
        </span>
        <button className="btn btn-danger" onClick={this.handlerClick}>清除已完成任务</button>
      </div>
    )
  }
}