redux 版 todolist

1.搭建环境

npx create-react-app react-redux-todolist

2.Mock 数据

首先,你需要准备好服务器程序,能提供服务器的 npm 包很多,例如:live-server、serve、json-server 都可以,我们这里使用 live-server 来演示

// 进入到test目录
live-serve

结果如下图

接下来,我们需要把请求转发到这台服务器上(http://localhost:5000/)

在src目录下,新建setupProxy.js文件

const proxy = require('http-proxy-middleware')

module.exports = function(app) {
  app.use(
    proxy('/api', {
      target: 'http://localhost:5000',
      changeOrigin: true,
    }),
  )
}

重启应用,配置才能生效

另一种比较简单mock数据的方法就是将mock数据放到public文件夹下,例如: public/mock/data.json 这是mock数据的地址,请求地址为:http://localhost:3000/mock/data.json

3.组件划分原则

1.解耦

降低单一模块或者组件的复杂度

2.复用

保证组件一致性,提升开发效率

3.掌握组件划分的颗粒度

不要把组件划分得过大或者过小,如果组件划分得过大,组件的复杂度就会上升,开发和维护起来就不是太方便,如果组件划分得过小,组件之间的通信就会变得非常频繁,同时文件数量也会上升,维护起来也不方便

4.组件划分一般根据一个独立的UI区域划分成一个组件

4.编写静态组件

1.开发过程解藕

静态页面和动态交互拆分开来写

2.组件开发顺序

可以是自上而下,或者自下而上

App -> TodoList -> Todo -> AddTodo -> Footer

App.js

import React, { Component } from 'react'
import AddTodo from './AddTodo'
import TodoList from './TodoList'
import Footer from './Footer'
const todoData = [
  {
    id: 1,
    content: '吃饭',
    complated: false,
  },
  {
    id: 2,
    content: '睡觉',
    complated: false,
  },
  {
    id: 3,
    content: '打豆豆',
    complated: true,
  },
]
const filter = 'all'
class App extends Component {
  render() {
    return (
      <div>
        <AddTodo></AddTodo>
        <TodoList todoData={todoData}></TodoList>
        <Footer filter={filter}></Footer>
      </div>
    )
  }
}

export default App

AddTodo.js

import React, { Component } from 'react'

class AddTodo extends Component {
  render() {
    return (
      <div>
        <input type="text" />
        <button>Add</button>
      </div>
    )
  }
}

export default AddTodo

Todolist.js

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

class TodoList extends Component {
  render() {
    let { todoData } = this.props
    return (
      <div>
        <ul>
          {todoData.map(item => {
            return <Todo key={item.id} {...item} />
          })}
        </ul>
      </div>
    )
  }
}

export default TodoList

Todo.js

import React, { Component } from 'react'

class Todo extends Component {
  render() {
    let { complated, content } = this.props
    return <li style={{ textDecoration: complated ? 'line-through' : 'none' }}>{content}</li>
  }
}

export default Todo

Footer.js

import React, { Component } from 'react'

class Footer extends Component {
  render() {
    let { filter } = this.props
    return (
      <div>
        <span>Show:</span>
        <button disabled={filter === 'all'}>All</button>
        <button disabled={filter === 'active'}>Active</button>
        <button disabled={filter === 'complated'}>Complated</button>
      </div>
    )
  }
}

export default Footer

5.如何设计State

什么是state?代表UI的完整且最小状态集合,1 必须是代表UI的,如果一个数据和UI无关,例如:定义一个定时器变量timer,这种就不算是state,2 必须是代表完整的UI状态同时还是最小的UI状态集合,不要去创建冗余的状态,

具体如何判断一个变量是不是state呢?

1、是否通过父组件props传入?如果一个值从父组件props传入,可以直接获取到的,并不是变化的,因此不必定义到state中

2、是否不会随着时间、交互操作变化?如果一个值不随时间或者交互操作变化就不是一个state,也不要定义到state中

3、是否可以通过其他的state或者props计算得到? 如果可以通过state或者props计算得到,那么说明这个变量是一种冗余的状态,也没有必要定义到state中

6.分析state存放的位置

1.state其实有双层含义

代表应用层UI的所有状态的集合

代表状态集合中的每一部分(待办事项列表、新增输入框文本、筛选条件)

2.分析State保存位置

我们在这里说的保存位置说的是集合中的每一部分到底保存到哪里

1. 确定依赖state的每一个组件

2. 如果某个state被多个组件依赖,寻找共同的父组件(状态上移)

实际举例:

在App.js中定义状态

this.state = {
  todos: todoData,
  filter: 'all',
}

Todolist组件和Footer组件中都依赖todos状态和filter状态,所以要将状态上移,放到App组件上

在AddTodo中定义状态

this.state = {
  text: '',
}

这个text状态只在AddTodo组件中被用到,所以不用上移,直接定义到自己组件内部

7.添加交互效果

App.js

import React, { Component } from 'react'
import AddTodo from './AddTodo'
import TodoList from './TodoList'
import Footer from './Footer'

class App extends Component {
  constructor(props) {
    super(props)
    this.state = {
      todos: [],
      filter: 'all',
    }
    this.nextTodoId = 0
  }
  render() {
    let todos = this.getVisiableTodos()
    return (
      <div>
        <AddTodo addTodo={this.addTodo}></AddTodo>
        <TodoList todoData={todos} toggleTodo={this.toggleTodo}></TodoList>
        <Footer filter={this.state.filter} setVisibilityFilter={this.setVisibilityFilter}></Footer>
      </div>
    )
  }
  getVisiableTodos() {
    return this.state.todos.filter(todo => {
      if (this.state.filter === 'active') {
        return !todo.complated
      } else if (this.state.filter === 'complated') {
        return todo.complated
      } else {
        return true
      }
    })
  }
  addTodo = text => {
    let newTodo = {
      id: this.nextTodoId++,
      content: text,
      complated: false,
    }
    let todos = [...this.state.todos, newTodo]
    this.setState({ todos })
  }
  toggleTodo = id => {
    let todos = this.state.todos.map(todo => {
      return todo.id === id ? { ...todo, complated: !todo.complated } : todo
    })
    this.setState({ todos })
  }
  setVisibilityFilter = filter => {
    this.setState({ filter })
  }
}

export default App

AddTodo.js

import React, { Component } from 'react'

class AddTodo extends Component {
  constructor(props) {
    super(props)
    this.state = {
      text: '',
    }
  }
  handleClick = () => {
    this.props.addTodo(this.state.text)
  }
  handleChange = e => {
    this.setState({ text: e.target.value })
  }
  render() {
    return (
      <div>
        <input type="text" value={this.state.text} onChange={this.handleChange} />
        <button onClick={this.handleClick}>Add</button>
      </div>
    )
  }
}

export default AddTodo

TodoList.js

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

class TodoList extends Component {
  render() {
    let { todoData, toggleTodo } = this.props
    return (
      <div>
        <ul>
          {todoData.map(item => {
            return (
              <Todo
                key={item.id}
                {...item}
                onClick={() => {
                  toggleTodo(item.id)
                }}
              />
            )
          })}
        </ul>
      </div>
    )
  }
}

export default TodoList

Todo.js

import React, { Component } from 'react'

class Todo extends Component {
  render() {
    let { complated, content, onClick } = this.props
    return (
      <li onClick={onClick} style={{ textDecoration: complated ? 'line-through' : 'none' }}>
        {content}
      </li>
    )
  }
}

export default Todo

Footer.js

import React, { Component } from 'react'

class Footer extends Component {
  render() {
    let { filter, setVisibilityFilter } = this.props
    return (
      <div>
        <span>Show:</span>
        <button onClick={() => setVisibilityFilter('all')} disabled={filter === 'all'}>
          All
        </button>
        <button onClick={() => setVisibilityFilter('active')} disabled={filter === 'active'}>
          Active
        </button>
        <button onClick={() => setVisibilityFilter('complated')} disabled={filter === 'complated'}>
          Complated
        </button>
      </div>
    )
  }
}

export default Footer