十年网站开发经验 + 多家企业客户 + 靠谱的建站团队
量身定制 + 运营维护+专业推广+无忧售后,网站问题一站解决
创新互联专注于阳曲企业网站建设,响应式网站开发,商城网站制作。阳曲网站建设公司,为阳曲等地区提供建站服务。全流程按需网站制作,专业设计,全程项目跟踪,创新互联专业和态度为您提供的服务
目录
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框架;
,输入框;
https://ant.design/components/input-cn/
输入框,能够让用户提交信息,使用接收回车键即提交信息(另,也可点一按键即提交,这是锦上添花),即键盘按下就是一个事件(与处理鼠标点击一样),在中;
输入框的“回车”和“按钮”是一个事件,增加按钮是锦上添花;
import { Input } from 'antd';
ReactDOM.render(, 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(, mountNode);
// import { Card } from 'antd';
import 'antd/lib/card/style';
// ReactDOM.render(
// // title="Card title" // extra={More} // style={{ width: 300 }} // > // Card content
//
Card content
//
Card content
// ,
// mountNode);
// export default class Create extends React.Component { //一定要导入React否则有异常
// render() {
// return (
//
//
//
// );
// }
// }
export default props => //缺省导出无状态组件,这样写就是让不在这里面定义业务的方法
//等价于onPressEnter={(event) => {props.onCreate(event)}};测试时可用onPressEnter={(...args) => console.log('args is ', args)}看打印出什么,就能决定参数怎么写
{/* {props.onCreate(event)}}/> */} //onPressEnter本质就是要关联一个单个参数的函数就行了,相当于绑定一个函数对象;onPressEnter实现的是一个函数定义,这个函数定义是一个参数,这个参数就是event对象,该箭头函数本质就是调用props.onCreate(title)函数,这个箭头函数只是作转发,完全可省略,用上面那种写法
注:
./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 (
)
}
}
ReactDom.render(
注:
若手动将Local Storage中的数据清掉,数组中仍有,这两处不会同步;
若再次刷新网页,内存数组中的会清掉;
增加的todo待办事宜,得显示出来;
CheckBox,多选框,用来选中(完成)和取消(未完成);
onChange,选中|取消时触发回调函数;
checked,是否选中,如在Todo.js中
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(
//
// mountNode);
// import { Card, Col, Row } from 'antd';
// ReactDOM.render(
//
//
//
//
//
//
//
//
//
//
//
//
//
// mountNode);
export default props => (
{props.todo.title}
)
./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 (
{/* {this.service.todos.map(
item =>
} */} //迭代所有todos元素,返回一个个React组件;如下图,要求迭代的元素有唯一的key,此处加上key={item.key}
{
[...this.service.todos.values()].map(
item =>
)
}
);
}
}
ReactDom.render(
在index.js的
./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(
./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(
//
//
//
//
//
//
//
//
//
//
//
// mountNode);
const Option =Select.Option;
export default props => (
)
./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 (
{/* {this.service.todos.map(
item =>
} */}
{/* {
[...this.service.todos.values()].map(
item =>
)
} */}
{
[...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 =>
)
} //迭代待办事宜时,加入数组的filter函数过滤
);
}
}