React实现todos

TodoMVC

官网实例

git clone https://github.com/tastejs/todomvc-app-template.git --depth 1

React的配置

  1. 可以使用脚手架
cnpm install -g create-react-app
create-react-app todos
# 支持es6,加上
cnpm install --save-dev babel-plugin-import react-app-rewired
  1. 这里我是自己配置的
    下载项目所需要的包
# 初始化package.json
cnpm init -y
# 下载所需要的包
cnpm i webpack webpack-cli html-webpack-plugin webpack-dev-server babel-loader@7 babel-core babel-preset-env babel-preset-stage-0 babel-plugin-transform-runtime less less-loader css-loader style-loader babel-preset-react -D
cnpm i react react-dom -S
# 可能需要用到路由
cnpm i react-router-dom -S
# 可能需要用到配置文件

项目文件
webpack.config.js

const path = require('path')
const htmlWebpackPlugin = require('html-webpack-plugin')

module.exports = {
  entry: path.join(__dirname, './src/main.js'),
  output: {
    path: path.join(__dirname, './dist'),
    filename: 'bundle.js'
  },
  plugins: [ // 插件
    new htmlWebpackPlugin({
      template: path.join(__dirname, './src/index.html'),
      filename: 'index.html'
    })
  ],
  module: {
    rules: [
      { test: /\.css$/, use: ['style-loader', 'css-loader'] }, // 如果想要启用 CSS 模块化,可以为 css-loader 添加 modules 参数即可
      { test: /\.less$/, use: ['style-loader', 'css-loader?modules&localIdentName=[name]_[local]-[hash:5]', 'less-loader'] },
      { test: /\.jsx?$/, use: 'babel-loader', exclude: /node_modules/ }
    ]
  }
}

.babelrc

{
    "presets": ["env", "stage-0","react"],
    "plugins": ["transform-runtime"]
  }

main.js

import React from 'react'
import ReactDom from 'react-dom'
import App from './App.jsx'
ReactDom.render(<App></App>,document.querySelector('#app'))

我们下载好的todomvc-app-template里面有个index.html,我们将其复制到我们的App.jsx

  • 根据模板引入的文件,我们需要下载css包cnpm i todomvc-common todomvc-app-css -S
  • 由于React的注释是{},所以我们需要把里面的html注释掉
  • class在React是关键字,className才是JS的类名,需要替换掉;还有for要改为htmlFor、autofocus改为autoFocus,value改为defaultValue,checked改为defaultChecked
  • App.jsx
import React from 'react'
import 'todomvc-common/base.css'
import 'todomvc-app-css/index.css'
export default class App extends React.Component{
    constructor(props){
        super(props)
    }
    render(){
        return <div>
		<section className="todoapp">
			<header className="header">
				<h1>todos</h1>
				<input className="new-todo" placeholder="What needs to be done?" autoFocus={true}/>
			</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" />
							<label>Taste JavaScript</label>
							<button className="destroy"></button>
						</div>
						<input className="edit" defaultValue="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" defaultValue="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>
		<footer className="info">
			<p>Double-click to edit a todo</p>
			<p>Template by <a href="http://sindresorhus.com">Sindre Sorhus</a></p>
			<p>Created by <a href="http://todomvc.com">you</a></p>
			<p>Part of <a href="http://todomvc.com">TodoMVC</a></p>
		</footer>
        </div>
    }
}

启动项目(在package.json配置启动项)

    "serve": "webpack-dev-server --open --port 3000 --hot"
cnpm run serve

界面
以上对React配置比较熟悉可以不用看,或则使用脚手架;想要自己写模板也可以;直接跳过

实现业务功能

渲染数据

  1. 模拟一个数据,在main引入
let todos = [
    {id:1,do:'learn vue',bool:false},
    {id:2,do:'learn react',bool:false},
    {id:3,do:'learn angular',bool:false},
]
// ReactDom.render(<App {...todos}></App>,document.querySelector('#app'))
ReactDom.render(<App data={todos}></App>,document.querySelector('#app'))
  1. 将数据保存在我们的App.jsx
    constructor(props){
        super(props)
        // 1. 将数据储存在我们的state状态里面
        this.state = {
            todos:props.data
        }
    }
  1. 渲染数据
				<ul className="todo-list">
                {/* 1.1.循环我们的数据 */}
                {this.state.todos.map((todo,index)=>{
                	return <li className=className={todo.bool?"completed":""} key={todo.id}>
                        <div className="view">
                            <input className="toggle" type="checkbox" onChange={()=>{this.changeCheckHandle(index)}} defaultChecked={todo.bool}/>
                            <label>{todo.do}</label>
                            <button className="destroy"></button>
                        </div>
                        <input className="edit" defaultValue={todo.do} />
                    </li>
                })}
				</ul>
  1. 当点击勾选按钮,(即checkbox改变时),那么数据重新改变
   changeCheckHandle = (index)=>{
        let todos = this.state.todos.map((item,i)=>{
			if(i==index){
				item.bool = !item.bool
			}
			return item
		})
		this.setState({
			todos
		})
		
    }
  1. 我们可以查看一下我们的数据是否已经改变
    查看数据

双击编辑数据

  1. 我们从模板可知,完成状态是给每一个todo(li)添加一个类editing,完成状态是completed
  2. 我们只需要在双击时给该条todo添加一个类editing即可进入编辑状态(在这里聚焦我试了好多方式总是有bug,后面我在试试后更)
  3. 当使用enter编辑成功,使用esc退出编辑
        this.state = {
			todos:props.data,
			newTodo:{}//这是用来判断是否进入编辑状态
        }
                	// return <li className={[todo.bool?"completed":"",this.state.newTodo==todo?"editing":""]} key={todo.id}>
                	return <li className={(todo.bool?"completed":"")+" "+(this.state.newTodo==todo?"editing":"")} key={todo.id}>
                        <div className="view">
                            <input className="toggle" type="checkbox" onChange={()=>{this.changeCheckHandle(index)}} defaultChecked={todo.bool}/>
                            <label onDoubleClick={()=>{this.editTodoHandle(todo)}}>{todo.do}</label>
                            <button className="destroy"></button>
                        </div>
						<input 
						className="edit"  
						autoFocus 
						defaultValue={todo.do} 
						ref="editItem"
						onKeyUp = {(e)=>{this.editedHandle(e,index)}}
						/>
                    </li>
	// 进入编辑状态
	editTodoHandle = (item)=>{
		this.refs.autofocus = true
		document.querySelectorAll('.edit')[0].autofocus = true
		document.querySelectorAll('.edit')[1].autofocus = true
		document.querySelectorAll('.edit')[2].autofocus = true
		this.setState({
			newTodo:item
		})
	}
	// 编辑完成
	editedHandle(e,index){
		// 如果是按退出键退出编辑状态=27
		if(e.keyCode==27){
			this.setState({
				newTodo:{}
			})
			return;
		}
		if(e.keyCode==13){
			let todos = this.state.todos.map((item,i)=>{
				if(i==index){
					item.do = e.target.value
				}
				return item
			})
			this.setState({
				todos,
				newTodo:{}
			})
		}	
	}

点击叉号.destroy按钮删除该条数据

  1. 绑定事件
onClick={()=>{this.delTodoHandle}}
  1. 点击删除该条数据(处理函数)
	delTodoHandle(index){
		let todos = this.state.todos
		todos.splice(index,1)
		// 保存数据
		this.setState({
			todos
		})
	}

解决现实item left未完成的数量

<span className="todo-count"><strong>{this.state.todos.filter((t)=>!t.bool).length}</strong>item left</span>

清除已完成Clear completed

  1. 绑定事件
<button className="clear-completed" onClick={this.clearCompletedHandle}>Clear completed</button>
  1. 清除已完成事件
	clearCompletedHandle(){
		let todos = this.state.todos.filter(t=>!t.bool)
		this.setState({
			todos
		})
	}

选中完成,未完成,所有的项目completed,active,all

  1. 监听路由的变化(window.location.hash
  2. 我们可以返回一个值,让todo每一项遍历是我们返回的值,而不是todos(当然也可以直接操作设置todos数据setState,不过要考虑改变了原来的数据todos
  3. 定义一个数组存放todos数据
        this.state = {
			todos:props.data,
			newTodo:{},
			newTodos:[] //用来拷贝todos
        }
  1. 定义一个函数处理路由规则,判断路由的哈希值
	hashChangeHandle(){
		// console.log(location.hash.startsWith('/active',1))
		// 这是正在完成项目,即未完成(bool:false)
		let newTodos = []
		if(location.hash.startsWith('/active',1)){
			newTodos = this.state.todos.filter(t=>!t.bool)
		}else if(location.hash.startsWith('/completed',1)){
			newTodos = this.state.todos.filter(t=>t.bool)
		}else{
			newTodos = this.state.todos
		}
		this.setState({
			newTodos
		})
	}
  1. 初始化就调用它(利用React的生命周期函数)
	componentWillMount(){
		this.hashChangeHandle()
	}
  1. 监听hash值的改变
	componentDidMount(){
		window.onhashchange = function(){
			// console.log(this)
			this.hashChangeHandle()
		}.bind(this)
	}
  1. 类的高亮,根据路由变化给类添加选中样式,这个比吃炒面还简单…
				<ul className="filters">
					<li>
						<a className={"/"==window.location.hash.substring(1)?"selected":""} href="#/" >All</a>
					</li>
					<li>
						<a className={"/active"==window.location.hash.substring(1)?"selected":""} href="#/active">Active</a>
					</li>
					<li>
						<a className={"/completed"==window.location.hash.substring(1)?"selected":""} href="#/completed">Completed</a>
					</li>
				</ul>

添加一条数据

				<input 
				className="new-todo" 
				ref="addTodo"
				placeholder="What needs to be done?" 
				autoFocus={true}
				onKeyUp={(e)=>{this.addTodoHandle(e)}}/>
	addTodoHandle(e){
		//如果文本框为空或则不是使用enter不执行任何操作
		let addVal = this.refs.addTodo.value
		let todos = this.state.todos
		if(e.keyCode!==13 || !addVal.length){
			return;
		}
		let id = todos.length?todos[todos.length-1].id+1:1
		let todo = {
			id,
			do:addVal,
			bool:false
		}
		todos.push(todo)
		this.setState({
			todos,
			newTodos:todos
		})
		// 清空
		this.refs.addTodo.value = ''
	}

全选与反选

  1. 先实现反选:即所有todo都是完成completed时,我们的箭头才是高亮,其余情况不高亮
  2. 我们首先使用defaultChecked进行绑定,然而,该属性只在初始的时候有用,怎么改变todo的选择都没用
<input id="toggle-all" className="toggle-all" type="checkbox" defaultChecked={this.state.newTodos.every(t=>t.bool)} ref="selectedCheck"/>
  1. 使用checkedLink={}又会报错,只能使用js操作checked来实现反选
    changeCheckHandle = (index)=>{
        let todos = this.state.todos.map((item,i)=>{
			if(i==index){
				item.bool = !item.bool
			}
			return item
		})
		this.setState({
			todos,
			newTodos:todos
		})
		this.refs.selectedCheck.checked=this.state.newTodos.every(t=>t.bool)
	}

结果
4. 全选:即点击三角图标,如果它高亮,所有的todo项都是完成

	// 全选
	toggleHandle = ()=>{
		let todos = this.state.todos
		let toggle = document.querySelectorAll('.toggle')
		todos.forEach(item => {
			item.bool = this.refs.selectedCheck.checked	
		})
		for(var i=0;i< toggle.length;i++){
			toggle[i].checked = this.refs.selectedCheck.checked	
		}
		this.setState({
			todos,
			newTodos:todos
		})
	}
 <input className="toggle" type="checkbox" onChange={()=>{this.changeCheckHandle(index)}} defaultChecked={todo.bool}/>

数据持久化(本地储存)

  1. 获取本地储存的todos没有则定义为空数组(main.js)
let todos = JSON.parse(localStorage.getItem('todos') || '[]')
ReactDom.render(<App data={todos}></App>,document.querySelector('#app'))
  1. 当todos数据有变化时,立刻储存
    shouldComponentUpdate(nextProps,nextState){
		localStorage.setItem('todos',JSON.stringify(nextState.todos))
		return true
	}

bug解决

  1. 我们使用了newTodos,那么清除已完成的也要设置newTodos
	clearCompletedHandle(){
		// console.log(this.state)
		let todos = this.state.todos.filter(t=>!t.bool)
		this.setState({
			todos,
			newTodos:todos
		})
	}
<button className="clear-completed" onClick={()=>{this.clearCompletedHandle()}}>Clear completed</button>
  1. 当添加一条数据时,三角按钮#toggle不会跟我们想象那样变化
    shouldComponentUpdate(nextProps,nextState){
		this.refs.selectedCheck.checked=this.state.newTodos.every(t=>t.bool)
		return true
	}
  1. 编辑状态
	editedHandle(e,index){
.......
			this.setState({
				todos,
				newTodos:todos,
				newTodo:{}
			})
		}	
	}
  1. 因为我没做过测试,所以可能有其它bug存在…

资料

子组件向父组件传值

猜你喜欢

转载自blog.csdn.net/weixin_41105030/article/details/89630382
今日推荐