目錄
todolist項目:
階段5,狀態的控制和改變:
Redux和Mobx,社區提供的狀態管理庫;
Redux,代碼優秀,使用嚴格的函數式編程思想,學習曲線陡峭,小項目使用的優勢不明顯;
Mobx,優秀穩定的庫,簡單方便(內部實現複雜),適合中小項目使用,使用面向對象的方式,易學習和接受,現使用非常廣泛;
https://mobx.js.org/
http://cn.mobx.js.org/
mobx實現了觀察者模式(訂閱+廣播模式),觀察者觀察某個目標,obserable目標對象發生了變化,會通知自己內部註冊了的observer觀察者;
./src/index.js
import React from 'react';
import ReactDom from 'react-dom';
import TodoApp from './component/TodoApp';
import TodoService from './service/service';
const service = new TodoService(); //service實例通過props屬性傳遞到組件中
ReactDom.render(<TodoApp service={service}/>, document.getElementById('root'));
./src/service/service.js
解決複選框改變,列表不刷新問題:
1、手動修改todos,如下例,相當於change過;
2、用一常量,如@observable changed=false(或changed=時間戳+隨機數),但這個變量要在render()中用到,哪怕寫{this.props.service.changed}都行反正要用到,否則不會刷新;
import store from 'store';
import {observable} from 'mobx';
export default class TodoService {
constructor() {
// super();
this.load();
}
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 = [];
@observable //觀察目標
todos = new Map();
create(title) {
// console.log('service');
const todo = {
key: TodoService.NAMESPACE + (new Date()).valueOf(),
title: title,
completed: false
};
// this.todos.push(todo);
this.todos.set(todo.key, todo);
store.set(todo.key, todo);
return todo;
}
setTodoState(key, checked) {
let todo = this.todos.get(key);
if (todo) {
todo.completed = checked;
store.set(key, todo);
}
let temp = this.todos; //改變觀察目標,解決Checkbox複選框改變列表不刷新問題
this.todos = {}; //同this.todos = () => {}
this.todos = temp;
}
}
./src/component/TodoApp.js
this.props.service.create(event.target.value),index.js已定義,此處用props屬性訪問;
之前的this.state=({todos: this.service.todos})註釋,當前使用mobx控制狀態;
import React from 'react';
import Create from './Create';
// import TodoService from '../service/service';
import Todo from './Todo';
import Filter from './Filter';
import {observer} from 'mobx-react'; //注意此處是mobx-react
@observer
export default class TodoApp extends React.Component {
constructor(props) {
super(props);
// this.service = new TodoService();
// this.state = ({todos: this.service.todos, filter: 'uncompleted'});
this.state = ({filter: 'uncompleted'});
}
// 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.props.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.props.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.props.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)} />
)
}
</div>
);
}
}
./src/component/{Create.js,Todo.js,Filter.js}不動;
階段6,getter、setter:
類似py的property裝飾器;
./src/service/service.js
import store from 'store';
import {observable} from 'mobx';
export default class TodoService {
constructor() {
// super();
this.load();
}
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 = [];
@observable
_todos = new Map();
get todos() { //getter
return this._todos;
}
create(title) {
// console.log('service');
const todo = {
key: TodoService.NAMESPACE + (new Date()).valueOf(),
title: title,
completed: false
};
// this.todos.push(todo);
this._todos.set(todo.key, todo);
store.set(todo.key, todo);
let temp = this._todos; //類似setter;這三句放到此處,解決新創建的待辦事宜的刷新問題
this._todos = {};
this._todos = temp;
return todo;
}
setTodoState(key, checked) {
let todo = this._todos.get(key);
if (todo) {
todo.completed = checked;
store.set(key, todo);
}
let temp = this._todos; //setter
this._todos = {}; //this.todos = () => {}
this._todos = temp;
}
}
./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';
import {observer} from 'mobx-react';
@observer
export default class TodoApp extends React.Component {
constructor(props) {
super(props);
// this.service = new TodoService();
// this.state = ({todos: this.service.todos, filter: 'uncompleted'});
this.state = ({filter: 'uncompleted'});
}
// 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.props.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.props.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.props.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)} />
)
}
</div>
);
}
}
階段7,過濾數據方法調整:
將過濾數據的filter,移到service.js中;
./src/service/service.js
import store from 'store';
import {observable} from 'mobx';
export default class TodoService {
constructor() {
// super();
this.load();
}
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 = [];
@observable
_todos = new Map();
@observable //添加觀察目標對象
filter = 'uncompleted';
get todos() { //getter,_todos變量
// return this._todos;
return [...this._todos.values()].filter(
item => {
let fs = this.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;
}
}
);
}
create(title) {
// console.log('service');
const todo = {
key: TodoService.NAMESPACE + (new Date()).valueOf(),
title: title,
completed: false
};
// this.todos.push(todo);
this._todos.set(todo.key, todo);
store.set(todo.key, todo);
let temp = this._todos;
this._todos = {};
this._todos = temp;
return todo;
}
setTodoState(key, checked) {
let todo = this._todos.get(key);
if (todo) {
todo.completed = checked;
store.set(key, todo);
}
let temp = this._todos;
this._todos = {}; //this.todos = () => {}
this._todos = temp;
}
setTodoFilter(value) {
this.filter = value;
}
}
./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';
import {observer} from 'mobx-react';
@observer
export default class TodoApp extends React.Component {
constructor(props) {
super(props);
// this.service = new TodoService();
// this.state = ({todos: this.service.todos, filter: 'uncompleted'});
// this.state = ({filter: 'uncompleted'}); //filter使用mobx管理
}
// 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.props.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.props.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});;
this.props.service.setTodoFilter(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.props.service.todos.map(
item => <Todo key={item.key} todo={item} onChange={this.handleCheckedChange.bind(this)} />
)
}
</div>
);
}
}
階段8,@computed的使用:
mobx提供了@computed裝飾器,可用在任意類的屬性的getter上,它所依賴的值發生了變化就重新計算,否則直接返回上次計算的結果;
使用@computed裝飾get todos();
./src/service/service.js
import {observable, computed} from 'mobx';
export default class TodoService {
……
@computed //程序員感知不到變化,觀察對象_todos和filter任意一個變化都會重新計算
get todos() {
// return this._todos;
return [...this._todos.values()].filter(
item => {
let fs = this.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;
}
}
);
}
……
}
階段9,完成狀態,Symbol類型:
Symbol類型,是JS中的基本類型;
是ES6新增的主數據類型,是一種特殊的、不可變的數據類型;
Symbol([description]),description是可選的字符串;
不可使用new關鍵字,生成的不是對象,直接用函數調用方式使用;
Symbol每次返回一個獨一無二的值,即便2次的描述一樣,描述只是爲了使人閱讀方便,便於區分不同的Symbol值;
例:
let sym0 = Symbol();
let sym1 = Symbol();
let sym2 = Symbol('symbol2');
let sym3 = Symbol('symbol2');
console.log(sym0 === sym1);
console.log(sym1 === sym2);
輸出:
false
false
./src/service/service.js
import store from 'store';
import {observable, computed} from 'mobx';
const ALL = Symbol('all');
const COMPLETED = Symbol('completed');
const UNCOMPLETED = Symbol('uncompleted');
export default class TodoService {
constructor() {
// super();
this.load();
}
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::';
static TODOSTATES = {
// all: 'all',
all: ALL,
// completed: 'completed',
completed: COMPLETED,
// uncompleted: 'uncompleted'
uncompleted: UNCOMPLETED
}
// todos = [];
@observable
_todos = new Map();
@observable
// filter = 'uncompleted';
filter = TodoService.TODOSTATES.uncompleted;
@computed
get todos() {
// return this._todos;
return [...this._todos.values()].filter(
item => {
let fs = this.filter;
// if(fs === 'all') {
if(fs === TodoService.TODOSTATES.all) { //只能用類.屬性這種方式,不可以fs === Symbol('all'),這樣相當於重新調用Symbol()是個新值;===常用,嚴格相等;==要做隱式轉換,不用
return true;
// } else if(fs === 'completed') {
} else if(fs === TodoService.TODOSTATES.completed) {
// if(item.completed === true)
// return true;
// else
// return false;
return item.completed === true;
// } else if(fs === 'uncompleted') {
} else if(fs === TodoService.TODOSTATES.uncompleted) {
// if(item.completed === false)
// return true;
// else
// return false;
return item.completed === false;
}
}
);
}
create(title) {
// console.log('service');
const todo = {
key: TodoService.NAMESPACE + (new Date()).valueOf(),
title: title,
completed: false
};
// this.todos.push(todo);
this._todos.set(todo.key, todo);
store.set(todo.key, todo);
let temp = this._todos;
this._todos = {};
this._todos = temp;
return todo;
}
setTodoState(key, checked) {
let todo = this._todos.get(key);
if (todo) {
todo.completed = checked;
store.set(key, todo);
}
let temp = this._todos;
this._todos = {}; //this.todos = () => {}
this._todos = temp;
}
setTodoFilter(value) {
// this.filter = value;
this.filter = TodoService.TODOSTATES[value];
}
}
注:
./src/component/Filter.js
<Select style={{ width: 120 }} defaultValue="uncompleted" onChange={value => props.onChange(value)}>
antd在defaultValue={TodoService.TODOSTATE.uncompleted},不支持傳遞類.屬性這種,只能用字符串;
階段10,axios,整合前後端:
axios是一個異步HTTP庫,可用在瀏覽器或nodejs中;
例:
axios.get('/user?ID=12345')
.then(function (response) { //返回200 OK的處理,response是返回的數據(區別於服務器的響應),具體看MIME類型,可以是picture、下載內容等
console.log(response);
})
.catch(function (error) { //異常的處理
console.log(error);
});
注:
前端要操作DB,通過ajax或jquery(早期用)-->web服務器;
url + method --> restful;
post #add
put #modify
store.set(todo.key, todo) #當前是存儲在瀏覽器本地的Local Storage,實際是要存儲在數據庫上,這塊需改造
例:
webpack.config.dev.js #開發的win主機上更改
devServer: {
compress: true,
port: 3000,
publicPath: '/assets/',
hot: true,
inline: true,
historyApiFallback: true,
stats: {
chunks: false
},
proxy: {
'/api': {
target: 'http://192.168.23.134:80', //nginx主機配置
changeOrigin: true
}
}
}
$ npm run build #在項目根下打包
]# pwd #nginx主機上操作
/ane/packages
]# tar xf tengine-1.2.3.tar.gz
]# cd tengine-1.2.3/
]# yum -y install gcc openssl-devel pcre-devel
]# ./configure
]# make && make install
]# cd /usr/local/nginx/
]# vim conf/nginx.conf
server {
listen 80;
server_name localhost;
#charset koi8-r;
#access_log logs/host.access.log main;
location /api/ {
proxy_pass http://192.168.7.144:8080; #win主機上運行的py開發的後臺app
}
location / {
root html;
index index.html index.htm;
}
]# tree ./ #將打好包的index.html和app-56350ea8.js放到nignx上,app-*.js要放到assets/下
./
├── 50x.html
├── assets
│ └── app-56350ea8.js
└── index.html
]# sbin/nginx #sbin/nginx -s reload,動態加載配置
]# netstat -tnulp | grep nginx
tcp 0 0 0.0.0.0:80 0.0.0.0:* LISTEN 2280/nginx: master
http://192.168.23.134/
注:
nginx配置中先註釋location /api/ {……}這段,在create待辦事宜時報404;
nginx配置中有location /api/ {……}這段,在create待辦事宜時報502,因爲後端還沒開啓py開發的後臺app;
win主機的pycharm中運行:
from aiohttp import web, log
import json
import logging
async def indexhandle(request: web.Request):
return web.Response(text='welcome to pyserver', status=200)
async def handle(request: web.Request):
print(request.match_info)
print(request.query_string)
return web.Response(text=request.match_info.get('id', '0000'), status=200)
async def todopost(request: web.Request): #協程函數
print(request.method)
print(request.match_info)
print(request.query_string)
print(request.json())
js = await request.json() #協程中用,同yield from
print(js, type(js))
text = dict(await request.post())
print(text, type(text))
js.update(text)
res = json.dumps(js)
print(res)
return web.Response(text=res, status=201) #201狀態碼錶示Created
app = web.Application()
app.router.add_get('/', indexhandle)
app.router.add_get('/{id}', handle)
app.router.add_post('/api/todo', todopost)
app.logger.setLevel(level=logging.NOTSET)
web.run_app(app, host='0.0.0.0', port=8080)
輸出:
======== Running on http://0.0.0.0:8080 ========
(Press CTRL+C to quit)
POST
<MatchInfo {}: <ResourceRoute [POST] <PlainResource /api/todo> -> <function todopost at 0x0000000003997A60>>
<coroutine object BaseRequest.json at 0x0000000003970F68>
{'completed': False, 'key': 'todo::1543038271618', 'title': 'test'} <class 'dict'>
{} <class 'dict'>
{"completed": false, "key": "todo::1543038271618", "title": "test"}
http://192.168.23.134