目录
todolist项目:... 1
antd:... 2
阶段1:... 2
服务层实现:... 2
视图层实现,输入框处理:... 3
阶段2,列表显示、更改todo的完成状态:... 6
阶段3,项目目录调整:... 11
阶段4,数据过滤:... 12
todo,待办;
人机交互,实质就是增删改查;
前端MVC框架、后端MVC框架;
注:
开发环境用:npm + webpack + babel;
部署是另一套东西;
国际化:
中、英两版;
复杂的网页只作字符串转换并不合适;
antd中的国际化;
瀑布式开发设计(传统,需求调研,各种设计,时间周期长);
敏捷开发(快速迭代);
异步提交;部分刷新;事件通知机制;重绘DOM树;
ant design,蚂蚁金服开源的React UI库;
https://ant.design/index-cn
https://ant.design/docs/react/introduce-cn
用户提交,需要表单控件,对于React来说也是一个组件,但这些组件需要用户看到,为了美观引入antd;
$ npm i antd --save
……
+ antd@2.13.14
added 68 packages and updated 1 package in 44.219s
需求分析:
分层:
视图层,负责显示数据,每一个React组件一个xxx.js文件;
服务层,负责业务数据处理逻辑,命名为xxxService.js文件;
Model层,负责数据,用Local Storage代替;
文件均在项目根下的./src/里;
注:
服务层要与视图层划分清楚,这比较麻烦;
./src/service.js
JS不支持静态变量,但支持静态方法;
但在babel下可用static定义静态变量;
import store from 'store';
export default class TodoService { //与组件无关,是类,大驼峰
static NAMESPACE = 'todo::'; //前缀,查看https://www.zhihu.com/的Application中Local Storage,相同类型的用同一个前缀,易于区分;看业务情况分段,一般最多2段即可,否则代码复杂
todos = []; //用数组存储待办事宜,是在内存中,刷新网页后会清空,所以必须存在Local Storage中
create(title) { //提供create方法,创建todo,把数据存储到Local Storage上(Local Storage当作是后台数据库,持久化用),同时插入到todos数组中;title从browser端来,是用户提交的输入,在视图层提供用户提交的文本框
console.log('service');
const todo = {
key: TodoService.NAMESPACE + (new Date()).valueOf(), //毫秒时间戳
title: title,
completed: false
};
this.todos.push(todo);
store.set(todo.key, todo);
return todo;
}
}
注:
前端的Model层相对简单,不用ORM工具,而是用browser的Local Storage模拟的存储;
后端的Model层要用ORM框架;
<Input .../>,输入框;
https://ant.design/components/input-cn/
输入框,能够让用户提交信息,使用接收回车键即提交信息(另,也可点一按键即提交,这是锦上添花),即键盘按下就是一个事件(与处理鼠标点击一样),在<Input/>中;
输入框的“回车”和“按钮”是一个事件,增加按钮是锦上添花;
import { Input } from 'antd';
ReactDOM.render(<Input placeholder="Basic usage" />, mountNode);
addonBefore,前缀标签;
addonAfter,后置标签;
placeholder,占位词;
onPressEnter,表示光标在输入框中,按下了回车键触发;
size="small",小输入框,large大输入框;
./src/Create.js
import React from 'react';
import { Input,Card } from 'antd'; //组合使用Input和Card组件
// import { Input } from 'antd';
import 'antd/lib/input/style'; //样式导入
// ReactDOM.render(<Input placeholder="Basic usage" />, mountNode);
// import { Card } from 'antd';
import 'antd/lib/card/style';
// ReactDOM.render(
// <Card
// title="Card title"
// extra={<a href="#">More</a>}
// style={{ width: 300 }}
// >
// <p>Card content</p>
// <p>Card content</p>
// <p>Card content</p>
// </Card>,
// mountNode);
// export default class Create extends React.Component { //一定要导入React否则有异常
// render() {
// return (
// <Card title='请输入待办事宜' style={{ width: 300 }}>
// <Input placeholder="Basic usage" onPressEnter={this.props.onCreate} />
// </Card>
// );
// }
// }
export default props => //缺省导出无状态组件,这样写就是让不在这里面定义业务的方法
<Card title='请输入待办事宜' style={{ width: 300 }}>
<Input placeholder="Basic usage" onPressEnter={props.onCreate}/> //等价于onPressEnter={(event) => {props.onCreate(event)}};测试时可用onPressEnter={(...args) => console.log('args is ', args)}看打印出什么,就能决定参数怎么写
{/* <Input placeholder="Basic usage" onPressEnter={(event) => {props.onCreate(event)}}/> */} //onPressEnter本质就是要关联一个单个参数的函数就行了,相当于绑定一个函数对象;onPressEnter实现的是一个函数定义,这个函数定义是一个参数,这个参数就是event对象,该箭头函数本质就是调用props.onCreate(title)函数,这个箭头函数只是作转发,完全可省略,用上面那种写法
</Card>
注:
./src/index.js
import React from 'react';
import ReactDom from 'react-dom';
import Create from './Create';
import TodoService from './service';
class Root extends React.Component {
constructor(props) {
super(props);
this.service = new TodoService(); //将业务的TodoService实例化,作为Root实例的属性
}
// handleCreate(...args) { //测试用,看打印的信息,可知参数如何写;MDN里html帮助,W3CSchool中的帮助
// console.log('Root handlerCreate');
// console.log(this);
// console.log('args is ', args);
// }
handleCreate(event) { //该函数处理Input中获取到的用户的输入,及拿到数据后调用业务层service.js中相应的方法处理
console.log('Root handleCreate');
console.log(this);
console.log('event is ', event.target, event.target.value);
this.service.create(event.target.value); //数据持久化,拿到用户输入的数据,之后的处理(不应该在Create.js组件里写类似这样的方法,数据交给index.js的Root组件处理,这就需要组件间数据共享,使用props),应调用service.js中TodoService类的create方法,构造器中有实例化该类
}
render() {
return (
<div>
<Create onCreate={this.handleCreate.bind(this)}/> //自定义组件属性onCreate,其它名字也可,约定这样写,用于注入数据处理函数到Create组件的props中;此处要绑定Root的this,否则handleCreate中打印的是Create元素的this
</div>
)
}
}
ReactDom.render(<Root />, document.getElementById('root'));
注:
若手动将Local Storage中的数据清掉,数组中仍有,这两处不会同步;
若再次刷新网页,内存数组中的会清掉;
增加的todo待办事宜,得显示出来;
CheckBox,多选框,用来选中(完成)和取消(未完成);
onChange,选中|取消时触发回调函数;
checked,是否选中,如在Todo.js中<Checkbox checked={props.todo.completed} .../>;
https://ant.design/components/checkbox-cn/
Grid栅格系统:
布局方案,ant desigh和bootstrap很像,都使用一套栅格系统,使用24栅格,即每一个内部都能切分为24份;
栅格卡片:
https://ant.design/components/card-cn/
例:
m.values()和m.keys()是迭代器(惰性求值),而array中的forEach()是立即返回;
m = new Map();
m.set(1,'a');
m.set(2,'b');
m.set(3,'c');
console.log(m);
let t = m.forEach((value,key) => console.log(key, '==', value)); //forEach方法遍历后,没有返回值
console.log(t);
t = [...m.values()].map(item => item + 1); //map方法遍历后,有返回值
console.log(t);
k = [...m.keys()].map(item => item + 1);
console.log(k);
输出:
Map { 1 => 'a', 2 => 'b', 3 => 'c' }
1 '==' 'a'
2 '==' 'b'
3 '==' 'c'
undefined
[ 'a1', 'b1', 'c1' ]
[ 2, 3, 4 ]
./src/Todo.js,构建todo组件:
import React from 'react';
import { Checkbox, Card, Col, Row } from 'antd';
import 'antd/lib/checkbox/style';
import 'antd/lib/Card/style';
import 'antd/lib/Col/style';
import 'antd/lib/Row/style';
// import { Checkbox } from 'antd';
// function onChange(e) {
// console.log(`checked = ${e.target.checked}`);
// }
// ReactDOM.render(
// <Checkbox onChange={onChange}>Checkbox</Checkbox>,
// mountNode);
// import { Card, Col, Row } from 'antd';
// ReactDOM.render(
// <div style={{ background: '#ECECEC', padding: '30px' }}>
// <Row gutter={16}>
// <Col span={8}>
// <Card title="Card title" bordered={false}>Card content</Card>
// </Col>
// <Col span={8}>
// <Card title="Card title" bordered={false}>Card content</Card>
// </Col>
// <Col span={8}>
// <Card title="Card title" bordered={false}>Card content</Card>
// </Col>
// </Row>
// </div>,
// mountNode);
export default props => (
<Card style={{ width: 300 }}>
<Row>
<Col span={4}>
<Checkbox checked={props.todo.completed} onChange={event => props.onChange(props.todo.key, event.target.checked)} /> //若没有checked属性,刷新页面后,完成状态的待办事宜不会选中,但Local Storage中的completed是true
</Col>
<Col span={20}>
{props.todo.title}
</Col>
</Row>
</Card>
)
./src/index.js
import React from 'react';
import ReactDom from 'react-dom';
import Create from './Create';
import TodoService from './service';
import Todo from './Todo';
class Root extends React.Component {
constructor(props) {
super(props);
this.service = new TodoService();
this.state = ({todos: this.service.todos}) //若无此句,新增title后,Root的state没变化,页面不会刷新导致看不到新增的,而在Local Storage中是有的
}
// handleCreate(...args) {
// console.log('Root handlerCreate');
// console.log(this);
// console.log('args is ', args);
// }
handleCreate(event) {
// console.log('Root handleCreate');
// console.log(this);
// console.log('event is ', event.target, event.target.value);
this.service.create(event.target.value);
this.setState({todos: this.service.todos});
}
handleCheckedChange(key, checked) { //增加事件响应函数,handleCheckedChange(event),event.target.checked=false|true;
console.log('handleCheckedChange', key, checked);
this.service.setTodoState(key, checked);
this.setState({todos: this.service.todos});
}
render() {
return (
<div>
<Create onCreate={this.handleCreate.bind(this)}/>
<br />
{/* {this.service.todos.map(
item => <Todo todo={item} key={item.key} onChange={this.handleCheckedChange.bind(this)}/>)
} */} //迭代所有todos元素,返回一个个React组件;如下图,要求迭代的元素有唯一的key,此处加上key={item.key}
{
[...this.service.todos.values()].map(
item => <Todo key={item.key} todo={item} onChange={this.handleCheckedChange.bind(this)} />
)
}
</div>
);
}
}
ReactDom.render(<Root />, document.getElementById('root'));
在index.js的<Todo />组件中,加key={item.key}
./service.js
import store from 'store';
export default class TodoService {
constructor() {
// super();
this.load(); //刷新页面后,从Local Storage装载数据,否则刷新页面后没有数据
}
load() {
store.each((value,key) => {
if (key.startsWith(TodoService.NAMESPACE)) //前缀即表示业务,防止与其它业务冲突,只过滤指定的业务
// this.todos.push(value);
this.todos.set(key, value);
});
console.log(this.todos);
}
static NAMESPACE = 'todo::';
// todos = [];
todos = new Map(); //遍历数组找到key匹配的,为高效使用ES6提供的Map类型
create(title) {
// console.log('service');
const todo = { //todo有3个属性,key、title、completed,completed表示完成状态
key: TodoService.NAMESPACE + (new Date()).valueOf(),
title: title,
completed: false
};
// this.todos.push(todo);
this.todos.set(todo.key, todo); //存储todo
store.set(todo.key, todo); //持久化todo
return todo;
}
setTodoState(key, checked) {
let todo = this.todos.get(key);
if (todo) {
todo.completed = checked;
store.set(key, todo); //同步Local Storage
}
}
}
注:
./src/component/{Create.js,Todo.js,TodoApp.js,Filter.js} #渲染的事情归TodoApp负责,并管理所有的状态;Create负责显示文本框,接收用户的输入;Todo负责每一条待办事宜的显示;Filter负责状态的切换
./src/service/service.js #负责业务的处理,为了简单,把数据处理也放在这里
./src/index.js
./src/index.js
import React from 'react';
import ReactDom from 'react-dom';
import TodoApp from './component/TodoApp';
ReactDom.render(<TodoApp />, document.getElementById('root'));
./src/component/TodoApp.js
import React from 'react';
import Create from './Create';
import TodoService from '../service/service';
import Todo from './Todo';
export default class Root extends React.Component {
……
}
过滤什么状态的待办事宜,有3种,未完成、完成、全部;
使用antd的select:
https://ant.design/components/select-cn/
./src/component/Filter.js
import React from 'react';
import {Select, Card, Row, Col} from 'antd';
import 'antd/lib/select/style';
import 'antd/lib/card/style';
import 'antd/lib/row/style';
import 'antd/lib/col/style';
// import { Select } from 'antd';
// const Option = Select.Option;
// function handleChange(value) {
// console.log(`selected ${value}`);
// }
// ReactDOM.render(
// <div>
// <Select defaultValue="lucy" style={{ width: 120 }} onChange={handleChange}>
// <Option value="jack">Jack</Option>
// <Option value="lucy">Lucy</Option>
// <Option value="disabled" disabled>Disabled</Option>
// <Option value="Yiminghe">yiminghe</Option>
// </Select>
// <Select defaultValue="lucy" style={{ width: 120 }} disabled>
// <Option value="lucy">Lucy</Option>
// </Select>
// </div>,
// mountNode);
const Option = Select.Option;
export default props => (
<Card style={{ width: 300 }}>
<Row>
<Col span="4"></Col>
<Col span="20">
<Select style={{ width: 120 }} defaultValue="uncompleted" onChange={value => props.onChange(value)}>
<Option value="all">所有</Option>
<Option value="completed">完成</Option>
<Option value="uncompleted">未完成</Option>
</Select>
</Col>
</Row>
</Card>
)
./src/component/TodoApp.js
import React from 'react';
import Create from './Create';
import TodoService from '../service/service';
import Todo from './Todo';
import Filter from './Filter';
export default class Root extends React.Component {
constructor(props) {
super(props);
this.service = new TodoService();
this.state = ({todos: this.service.todos, filter: 'uncompleted'})
} //在state的属性中加入filter
// handleCreate(...args) {
// console.log('Root handlerCreate');
// console.log(this);
// console.log('args is ', args);
// }
handleCreate(event) {
// console.log('Root handleCreate');
// console.log(this);
// console.log('event is ', event, event.target, event.target.value);
this.service.create(event.target.value);
this.setState({todos: this.service.todos});
}
handleCheckedChange(key, checked) { //handleCheckedChange(event),event.target.checked=false|true
console.log('handleCheckedChange', key, checked);
this.service.setTodoState(key, checked);
this.setState({todos: this.service.todos});
}
handleFilterChange(value) { //增加事件处理函数
// console.log('~~~~~~~', args);
// console.log(this);
console.log(value);
this.setState({filter: value});;
}
render() {
return (
<div>
<Create onCreate={this.handleCreate.bind(this)}/>
<Filter onChange={this.handleFilterChange.bind(this)}/>
<br />
{/* {this.service.todos.map(
item => <Todo todo={item} key={item.key} onChange={this.handleCheckedChange.bind(this)}/>)
} */}
{/* {
[...this.service.todos.values()].map(
item => <Todo key={item.key} todo={item} onChange={this.handleCheckedChange.bind(this)} />
)
} */}
{
[...this.service.todos.values()].filter(
item => {
let fs = this.state.filter;
if(fs === 'all') {
return true;
} else if(fs === 'completed') {
// if(item.completed === true)
// return true;
// else
// return false;
return item.completed === true;
} else if(fs === 'uncompleted') {
// if(item.completed === false)
// return true;
// else
// return false;
return item.completed === false;
}
}
).map(
item => <Todo key={item.key} todo={item} onChange={this.handleCheckedChange.bind(this)} />
)
} //迭代待办事宜时,加入数组的filter函数过滤
</div>
);
}
}
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。