功能点
- 输入框输入内容回车, 增加一条记录, 同时输入框内容被清空
- 双击每一条记录的文字内容, 进行编辑修改, 回车或者点击其他区域完成修改
- 点击每一条记录前面的圆圈, 切换该记录的完成或已完成状态, 圆圈和后面的文字内容随着状态的切换均有样式变化
- 每一条记录均有删除图标, 点击完成该条记录的删除操作
- 动态显示未完成事项的个数
- 三个按钮
All
,Active
,Completed
可完成对当前所有事项, 未完成事项, 已完成事项的查询显示 - 可一次性清除所有已完成事项
- 所有数据的永久性存储, 即刷新页面或再次启动项目数据仍然保持上次操作的数据记录
增
使用antd的Input+Form控件实现输入框UI, 利用getFieldDecorator()
将Input和表单进行双向绑定,每次用户向输入框输入内容并回车后,触发回调事件,通过getFieldsValue()
拿到输入框的内容,并将该内容连同iscompleted
和id
属性作为一个对象推入数组list
中,发送dispatch请求完成list的更新。
查
通过自定义不同的页面路由,但使用同一个页面模板,完成全部事项、未完成事项和已完成事项的筛选显示。
在组件中import pathToRegexp from 'path-to-regexp'
,并传入location
对象,通过location.pathname
判断当前页面的路由。
对数组list
使用filter()
方法,根据当前的路由,过滤出所有iscompleted
为true
或false
的项。
每一条事项的展示放在component展示组件中,即对数组进行map()
遍历操作,返回一个子组件,对这个子组件传入这个数组数组的每一项,以及每一项的索引,作为每一条事项的展示组件完成每一条事项的展示。
let todos = []
if (list.length) {
let showList = list.filter((value) => {
//根据当前路由, 确定list数组中需要展示的项
switch (pathname){
case '/active': return value.iscompleted === false
case '/completed': return value.iscompleted === true
default: return true
}
})
todos = showList.map(function(item, index) {
//子组件用来定义每一项的展示, 传入展示项以及索引, 和每一个react组件所必须的唯一key值
return <TodoItem todo={item} data_key={index} key={index+1}/>
})
}
删
在点击事件中携带该项纪录的索引值data_key
发送dispatch请求,(react组件的唯一key值无法传入组件内部),利用数组的splice()
方法(改变原数组)完成数组项的删除操作.
deleteItem(state, action) {
state.list.splice(action.payload.data_key, 1)
localStorage.setItem('list',JSON.stringify(state.list))
return {
...state
}
}
改
容器组件的声明采用stateless写法, 展示组件的声明采用ES6写法class TodoItem extends Component{}
.
编辑的时候涉及两个状态的变化: isEditing
是否编辑以及editText
编辑框input
的内容。双击文字内容,改变isEditing
为true,使隐藏的input框便显示出来。
const ESCAPE_KEY = 27;
const ENTER_KEY = 13;
class TodoItem extends Component {
constructor(props) {
super(props);
this.state = {
isEditing: false,
editText: this.props.todo.item
}
}
changeEditState() {
this.setState({
isEditing: true
})
}
handleChange(e) {
console.log(e.target.value)
this.setState({
editText: e.target.value
});
}
handleSubmit(e) {
let val = this.state.editText.trim();
if (val) {
// dispatch save
console.log(val)
this.setState({
isEditing: false
})
}
}
handleKeyDown(event) {
if (event.which === ESCAPE_KEY) {
this.setState({
editText: this.props.todo.item,
isEditing: false
});
} else if (event.which === ENTER_KEY) {
this.handleSubmit(event);
}
this.props.dispatch({
type: `toDos/applyEdit`,
payload: {
editText: this.state.editText,
id: this.props.todo.id
}
})
}
componentDidUpdate(prevProps, prevState) {
//处理光标
if (!prevState.isEditing && this.state.isEditing) {
let node = ReactDom.findDOMNode(this.refs.editField);
node.focus();
console.log(node.value)
node.setSelectionRange(node.value.length, node.value.length);
}
}
render() {
const {
todo,
dispatch,
toDos
} = this.props;
}
return (
<div className={textClass} onDoubleClick={this.changeEditState.bind(this)} >{todo.item}</div>
<input type="text" ref="editField"
className={editClass}
value={this.state.editText}
onBlur={this.handleSubmit.bind(this)}
onKeyDown={this.handleKeyDown.bind(this)}
onChange={this.handleChange.bind(this)}/>
</div>
)
}
数据的存储
localStorage是html5提供的一种本地存储的方法,可以把数据存储在本地浏览器,下次打开后仍然可以获取到存储的数据,如果在存储的数据量小的时候可以起到代替数据库的功能,比cookies更有优越性。特点:永久性存储.
Web Storage API --点这里
localStorage.setItem()
方法用来创建新数据项和更新已存在的值。该方法接受两个参数——要创建/修改的数据项的键,和对应的值。
localStorage.getItem()
可以从存储中获取一个数据项。该方法接受数据项的键作为参数,并返回数据值。
目前所有的浏览器中都会把localStorage的值类型限定为string类型,这个在对日常比较常见的JSON对象类型需要一些转换.
在对数据进行增删改操作之后,即数据list
有所更改变化后,调用Storage.setItem()
方法实时更改替换上次存储的数据。
localStorage.setItem('list',JSON.stringify(data))
。
在model文件的subscriptions
中,每当进入页面路由,就从存储中取出所有数据。
history.listen(({ pathname, query }) => {
if (pathToRegexp(`/`).test(pathname) || pathToRegexp(`/completed`).test(pathname) || pathToRegexp(`/active`).test(pathname)) {
let data = localStorage.getItem('list') || ''//取出数据
dispatch({
type: 'updateState',
payload: {
list: JSON.parse(data)//将数据装换成JSON对象,更新全局list
}
})
}
})
总结
-
组件结构的设计
容器组件和展示组件: 前者关注逻辑的处理和状态的变化, 后者设计成可复用的公共组件, 不涉及状态的处理, 只负责展示.
//子组件
let todos = showList.map(function(item, index) {
//每一条事项
return <TodoItem todo={item} data_key={index} key={index+1}/>
})
let footer
if (list.length) {
//操作按钮行
footer = <Footer list={list} onDeleteAllCompleted={onDeleteAllCompleted}/>
}
-
数据形式的设计
将每一条事项作为一个对象,放入一个数组中。数组的每一项包括每一条事项的内容item
, 是否完成iscompleted
, 以及id标识。通过查找id完成每一项数据的更改操作。
{
item: item,
iscompleted: false,
// isEditing: false,
id: id
}
- react中样式的处理
- CSS Modules的使用: 点击
import styles from './TodoItem.css'
//使用:
<div className={styles.todos}></div>
CSS Modules提供了compose组合方法来处理样式复用
.correct{
}
.correct::before{
position: relative;
top: -10px;
}
.btnbasic {
composes:correct; //compose组合
float: left;
border-radius: 50%;
border: 1px solid #e4dada;
width: 30px;
height: 30px;
margin: 10px 0;
text-align: center;
color: green;
cursor: pointer;
}
- classnames
首先安装npm install classnames
If you are using css-modules, or a similar approach to abstract class "names" and the real className values that are actually output to the DOM, you may want to use the bind variant.
在CSS Modules中使用classnames需要绑定变量bind
let classNames = require('classnames/bind')
//用法:
const itemClass = cx('item')
const btnClass = cx({
btnbasic: true,
'iconfont icon-correct': iscompleted
})
<div className={itemClass}></div>
<i className={btnClass}></i>