Map、Set都是ES6新的數據結構,都是新的內置構造函數,也就是說typeof的結果,多了兩個:css
Set 是不能重複的數組前端
Map 是能夠任何東西當作鍵的對象node
ES6 提供了新的數據結構 Set。它相似於數組,可是成員的值都是惟一的,沒有重複的值。react
let s = new Set(); s.add(1); s.add(2); s.add(3); s.add(3); s.add(3); s.add(4); s.add(5); console.log(s)
集合中添加數據用add()方法,會自動過濾已經有的元素。c++
最快的數組去重方法:git
let s = new Set([1,2,3,3,3,4,5]); console.log([...s])
let s = new Set([1,2,3,4,5]); console.log(s.size) console.log(s.has(5)) console.log(s.delete(5)) console.log(s)
集合的關注點是在元素裏面,而不關注順序,因此不提供s[0]來枚舉某項API,可是能夠轉爲數組[...s]github
JavaScript 的對象(Object),本質上是鍵值對的集合(Hash 結構),可是傳統上只能用字符串看成鍵。這給它的使用帶來了很大的限制。算法
爲了解決這個問題,ES6 提供了 Map 數據結構。它相似於對象,也是鍵值對的集合,可是「鍵」的範圍不限於字符串,各類類型的值(包括對象)均可以看成鍵。也就是說,Object 結構提供了「字符串—值」的對應,Map 結構提供了「值—值」的對應,是一種更完善的 Hash 結構實現。若是你須要「鍵值對」的數據結構,Map 比 Object 更合適。express
let m = new Map(); const o = {a:1,b:2}; m.set("haha", 123); m.set(o, 456) m.set(888,789) console.log(m) console.log(m.get(o))
使用get()來獲得元素的值,key是區分類型的。npm
所謂的「函數式編程」指的是程序中的函數的是「純函數」,就表示一個函數不改變傳入的參數。
咱們以前大量使用...、filter、map等等操做,略微麻煩,就有3個特別好用的函數式編程庫應運而生。
來自Facebook,是官方推薦的庫,immutable表示不可變。immutable老是返回新的對象,不會修改原對象。
immutable不是深拷貝對象,創造性的使用DIFF算法,有一個結構共享機制,它所作的是智能分析改變,讓改變後的元素能夠共享改變以前的一些結構。
Immutable 使用了 Structural Sharing(結構共享),即若是對象樹中一個節點發生變化,只修改這個節點和受它影響的父節點,其它節點則進行共享。
{ a:{"x":1 , "y":2}, b:{ c:{"x":3, "y":4}, d:{"x":5}, } }
{ a:{"x":1 , "y":2}, b:{ c:{"x":8, "y":4}, d:{"x":5}, } }
上面代碼中,顏色相同部分,仍是內存中同一個對象。
官方:http://facebook.github.io/immutable-js/docs/#/
安裝依賴:
npm install immutable --save
immutable提供了兩個數據類型:
List和Map是immutable本身封裝的一個類,List是不可變的數組,Map是不可變的對象。
fromJS() 將一個原生js數據類型轉換爲immutable類型的數據
toJS() 將一個immutable類型的數據轉換爲JS類型的數據。
List()和Map() 用來建立新的List和Map對象(將原生JS對象和數組轉換到不可變的Map和List)
immutable思惟:
先用fromJS()或List()或Map()將原生JS的數組、對象、變爲List、Map對象
而後再操做,好比對數組的push操做
最後.toJS()將immutable對象變爲原生JS的數組、對象
在Nodejs環境中引包:
var immutable = require("immutable"); var List = immutable.List const list1 = List(["白板","幺雞","三條","三萬"]) const list2 = list1.push("四條") console.log(list1.toJS()) console.log(list2.toJS())
會發現push以後,原數組沒有改變,只返回了新數組。
上面案例是用List(原數組)將數組變爲List對象,也能夠用fromJS。
var immutable = require("immutable"); var fromJS = immutable.fromJS const list1 = fromJS(["白板","幺雞","三條","三萬"]) const list2 = list1.push("四條") console.log(list1.toJS()) console.log(list2.toJS())
數組的頭尾操做,也不會改變原數組,都是返回新數組:
var immutable = require("immutable"); var fromJS = immutable.fromJS const list1 = fromJS(["白板","幺雞","三條","三萬"]) const list2 = list1.push("四條") const list3 = list1.pop() const list4 = list1.unshift("東風") const list5 = list1.shift() console.log(list1.toJS()) console.log(list2.toJS()) console.log(list3.toJS()) console.log(list4.toJS()) console.log(list5.toJS())
更改set
set表示更改下標爲2的項爲「二筒」,注意不會真的改變原數組,而是返回新數組:
const list1 = fromJS(["白板","幺雞","三條","三萬"]) const list2 = list1.set(2,"二筒");
對象也有set
const obj1 = fromJS({"a" : 1, "b" : 2 ,"c" : 3}) const obj2 = obj1.set("b",888) console.log(obj1.toJS()) console.log(obj2.toJS())
在數組中查找某一項,使用find(),尋找某一項下標用findIndex()
獲得Map對象的某一個鍵的值,要用get()方法
var immutable = require("immutable"); var fromJS = immutable.fromJS const data = fromJS([ {"id" : 1, "name" : "小明", "age" : 12}, {"id" : 2, "name" : "小紅", "age" : 12}, {"id" : 3, "name" : "小強", "age" : 13}, ]) const item = data.find(item=>item.get("id") == 2); console.log(item.toJS())
刪除用delete,刪除下標爲2的項
const list1 = fromJS([111,222,333,888,999]); const list2 = list1.delete(2); console.log(list1.toJS()); console.log(list2.toJS());
is()函數驗證是否相等
var {is , Map} = require("immutable"); let o1 = Map({a : 1, b : 2, c : 3}); let o2 = Map({a : 1, b : 2, c : 3}); console.log(o1 == o2); //在內存中不相等 console.log(is(o1,o2)); //在immutable世界中是相等
真實項目場景 - 增長todo,用set設置,用get獲取
var immutable = require("immutable"); var fromJS = immutable.fromJS const data = fromJS({ "todos" : [ {"id" : 1,"title" : "吃飯", "done" : false }, {"id" : 2,"title" : "睡覺", "done" : false }, {"id" : 3,"title" : "打豆豆", "done" : false }, ], "show" : "ALL" }) const newData = data.set( "todos", data.get("todos").push({"id" : 4,"title" : "打架", "done" : false}) ) console.log(newData.toJS())
真實項目場景 - 刪除id爲2的todo,使用delete(2)
//刪除id爲2的項 const newdata = data.set( "todos", data.get("todos").delete(data.get("todos").findIndex(item=>item.get("id")== 2) )); console.log(newdata.toJS());
方法2:
const newData = data.set( "todos", data.get("todos").filter(item=>item.get("id") != 2) )
真實項目場景 - 改變id爲2的項title爲「吃雞」
方法1
var index = data.get("todos").findIndex(item=>item.get("id") == 2); var item = data.get("todos").get(index) const newData = data.set("todos",data.get("todos").set(index, item.set("title","吃雞"))); console.log(newData.toJS())
方法2
const newData = data.set("todos",data.get("todos").map(item=>{ return item.get("id") == 2 ? item.set("title" , "吃雞") : item; }))
改造TodoList項目:
只改變reducers中的文件夾的寫法,其它地方一概不改。
state的數據依然是原生JS的數組和對象,只不過在reducer函數運行中,瞬間變成爲immutable的List和Map了,可是出去的時候就toJS了,裏外裏,state仍是原生數組、對象。
import {fromJS , toJS} from "immutable"; const initObj = { "todos" : [ {"id" : 1 , "title" : "吃飯" , "done" : false}, {"id" : 2 , "title" : "睡覺" , "done" : true}, {"id" : 3 , "title" : "打豆豆" , "done" : true} ], "show" : "ALL" } export default (state = initObj, action) => { //下面的語句很重要,變爲immutable對象 state = fromJS(state); if(action.type == "ADDTODO"){ return state.set("todos", state.get("todos").push( { "id" : state.get("todos").reduce((a,b)=>{ return b.get("id") > a ? b.get("id") : a }, 0) + 1, "title" : action.title, "done" : false } )).toJS(); }else if(action.type == "DELTODO"){ return state.set( "todos", state.get("todos").filter(item=>item.get("id") != action.id) ).toJS(); }else if(action.type == "CHANGETODO"){ return state.set( "todos", state.get("todos").map( item => item.get("id") == action.id ? item.set(action.k, action.v) : item ) ).toJS(); }else if(action.type == "CHANGESHOW"){ return state.set("show", action.show).toJS(); } return state.toJS(); }
純函數,也叫做「蘭姆達函數」,就是ramda這個詞。http://ramda.cn/
函數柯里化(Currying),是把接受多個參數的函數變換成接受一個單一參數的函數,而且返回接受餘下的參數,並且返回結果的新函數。柯里化函數就是逐步傳參,逐步縮小函數的適用範圍,逐步求解的過程。
簡單的加法函數:
function add(x,y){ return x + y; } add(2,3);//5
若是用函數柯里化接受兩個參數的函數變成單一參數,以下:
function add(x) { return function (y){ return x + y } } console.log(add(2)(3))
npm install ramda --save
數組尾插一項:
var R = require("ramda"); var arr1 = ["紅中","白板","幺雞"]; var arr2 = R.append("一筒", arr1) var arr3 = R.append("二筒")(arr1) console.log(arr1) console.log(arr2) console.log(arr3)
被操做的元素放到最後一個參數。
數組頭插一項:
var R = require("ramda"); var arr1 = ["紅中","白板","幺雞"]; var arr4 = R.prepend("發財",arr1) console.log(arr4)
改變對象的b屬性爲8
這裏使用了很是重要的方法R.lensProp()聚焦到某屬性,功能就是讓改變的值從原基礎上進行修改
var R = require("ramda"); const obj1 = {"a" : 1,"b" : 2,"c" : 3} const obj2 = R.set(R.lensProp("b") , 8, obj1) console.log(obj2)
刪除id爲2的項:
const state = { "todos": [ {"id" : 1, "title" : "吃飯", "done" : false}, {"id" : 2, "title" : "睡覺", "done" : false}, {"id" : 3, "title" : "打豆豆","done" : false} ], "show":"ALL" } //刪除id爲2的項 const newstate = R.set(R.lensProp("todos"), R.filter(item=>item.id != 2, state.todos) , state); console.log(newdata)
修改id爲2的項title爲吃雞
const newstate = R.set(R.lensProp("todos"),R.map(item => item.id == 2 ?
R.set(R.lensProp("title"),"吃雞",item) : item , state.todos), state);
修改show爲ONLYDONE
const newstate = R.set(R.lensProp("show") , "ONLYDONE", state);
基本上一條語句可以解決問題,再也不寫...了,而且相好比immutable,沒有Map、List和對象、數組的轉換。
仍是用todolist舉例,在reducers/index.js中修改,下面標黃色的語句的R.__是佔位符:
var R = require("ramda"); const initObj = { "todos" : [ {"id" : 1 , "title" : "吃飯" , "done" : false}, {"id" : 2 , "title" : "睡覺" , "done" : true}, {"id" : 3 , "title" : "打豆豆", "done" : true} ], "show" : "ALL" } export default (state = initObj, action) => { //R.__表示佔位符,下面調用setTodos時,就等於傳入了要更改爲爲的值 const setTodos = R.set(R.lensProp("todos"), R.__ , state); const setShow = R.set(R.lensProp("show") , R.__ , state); if(action.type == "ADDTODO"){ return setTodos(R.append({ "id" : state.todos.reduce((a,b) => b.id > a ? b.id : a, 0) + 1, "title" : action.title, "done" : false },state.todos)); }else if(action.type == "DELTODO"){ return setTodos(state.todos.filter(item=>item.id != action.id)); }else if(action.type == "CHANGETODO"){ return setTodos(state.todos.map(item=>item.id == action.id ? R.set(R.lensProp(action.k), action.v, item) : item)) }else if(action.type == "CHANGESHOW"){ return setShow(action.show); } return state; }
實際上underscore.js已經在「函數庫工具」輸給了lodash,lodash徹底能夠替代underscore。
中文:http://www.css88.com/doc/lodash/
npm install --save lodash
underscore有的函數,lodash全有,好比數組去重和最大最小值:
var _ = require("lodash"); var arr1 = [3,3,3,3,4,4,4,4,5,5,5,5,3]; var arr2 = _.uniq(arr1); console.log(arr1); console.log(arr2); console.log(_.max(arr1)); console.log(_.min(arr1));
lodash中有子包,叫作fp。fp是functional programing函數式編程的意思。
須要引入這個包
var fp = require("lodash/fp");
更改b屬性爲8:
var fp = require("lodash/fp"); var obj1 = {"a" : 1 , "b" : 2 , "c" : 3}; var obj2 = fp.set("b" , 8 , obj1); console.log(obj1); console.log(obj2);
和ramda同樣,被操做對象寫最後一個參數。
刪除
var fp = require("lodash/fp"); const state = { "todos" : [ {"id" : 1 , "title" : "吃1飯" , "done" : false}, {"id" : 2 , "title" : "吃2飯" , "done" : false}, {"id" : 3 , "title" : "吃3飯" , "done" : false} ], "show" : "ALL" }; //刪除id爲2的項 const newstate = fp.set("todos", state.todos.filter(item => item.id != 2), state); console.log(state); console.log(newstate);
增長:
const newstate = fp.set("todos", fp.concat( state.todos, { "id" : state.todos.reduce((a,b) => b.id > a ? b.id : a, 0) + 1, "title": "吃雞", "done" : false } ) , state);
修改id爲2的項的title爲「吃雞」
const newstate = fp.set("todos",state.todos.map(item=>item.id == 2 ? fp.set("title", "吃雞", item) : item), state);
若是碰見比較難的場景,此時能夠用克隆方法,好比在第2項之間插入一項
const _todos = fp.clone(state.todos) //更改克隆以後的數組 _todos.splice(2,0,{"id": 4,"title":"游泳","done":false}) //更改state const newstate = fp.set("todos", _todos, state) console.log(state) console.log(newstate)
const car = { "cars" : { "a" : [ { "name" : "奧迪" , "series" : [ { "name" : "A6", "type" : "豪華轎車" }, { "name" : "A4", "type" : "豪華轎車" } ] }, { "name" : "奧拓", "series" : [{"奧拓1號" : 2}] } ], "b" : [ {"奔馳" : 1} ] } } //改變A6的車系爲普通轎車 var newstate = fp.cloneDeep(car); newstate.cars.a[0].series[0].type = '普通轎車'; console.log(JSON.stringify(newstate))
import fp from "lodash/fp"; const initObj = { "todos" : [ {"id" : 1 , "title" : "吃飯" , "done" : false}, {"id" : 2 , "title" : "睡覺" , "done" : true}, {"id" : 3 , "title" : "打豆豆" , "done" : true} ], "show" : "ALL" } export default (state = initObj, action) => { if(action.type == "ADDTODO"){ return fp.set("todos" , fp.concat(state.todos , { "id" : state.todos.reduce((a,b) => b.id > a ? b.id : a , 0) + 1, "title" : action.title, "done" : false }), state); }else if(action.type == "DELTODO"){ return fp.set("todos" , state.todos.filter(item => item.id != action.id) , state); }else if(action.type == "CHANGETODO"){ return fp.set("todos", state.todos.map(item=>item.id == action.id ? fp.set(action.k,action.v,item) : item) , state); }else if(action.type == "CHANGESHOW"){ return fp.set("show" , action.show , state); } return state; }
咱們將全部前端的東西都放入www文件夾中。
Node.js寫app.js實現數據接口:
var express = require("express"); var app = express(); app.use(express.static("www")) app.get("/api",(req,res)=>{ res.json({"result":8}) }) app.listen(3000);
咱們如今有四個文件都沒有地方適合寫異步:components、actions、constants、reducer。
因此React提供了react-thunk包,thunk是中間件,所謂的中間件就是在發出action和reducer進行改變的中間,要作的事。
https://www.npmjs.com/package/redux-thunk
安裝依賴:
npm install --save redux-thunk
main.js
import React from "react"; import ReactDOM from "react-dom"; import { createStore, applyMiddleware} from "redux"; import { Provider } from 'react-redux' import thunk from 'redux-thunk' import reducers from "./reducers/index"; import logger from "redux-logger"; //引入父組件 import App from "./containers/App"; //建立Redux store 倉庫用來存放狀態 const store = createStore(reducers, applyMiddleware(logger, thunk)) ReactDOM.render( <Provider store={store}> <App></App> </Provider>, document.getElementById('app') )
components/counter/index.js組件,按鈕點擊以後作addServer:
<button onClick={()=>{this.props.counterActions.addServer()}}>加服務器那麼多</button>
此時actions/counterActions.js文件中,就能夠寫異步函數了:
兩步:第一步請求服務器數據,第二步發出action。將服務器的返回結果,當作載荷發給reducer。
import {ADD , MINUS , ADDSERVER} from "../constants/COUNTER.js"; //同步陣營,直接返回一個Action export const add = () => ({"type" : ADD}); export const minus = () => ({"type" : MINUS}); //異步有兩個(),第一個()接受按鈕傳的參數,第二個()是系統給你的dispatch和getState //export const addServer = (n)=> (dispatch, getState)=>{ // alert(n) // alert(dispatch) // alert(getState().counter.v) //} export const addServer = ()=> async (dispatch , getState) => { //發出Ajax請求,其實是fetch請求,fetch不是Ajax const {result} = await fetch("/api").then(data=>data.json()); //發action,由於惟一能改變reducer的方法就是dispath一個action dispatch({"type" : ADDSERVER , result}) }
constants/COUNTER.js
export const ADDSERVER = "ADDSERVER_COUNTER";
reducers/counter.js
import {ADD , MINUS , ADDSERVER} from "../constants/COUNTER.js"; export default (state = {"v" : 0} , action) => { if(action.type == ADD){ ... }else if(action.type == MINUS){ ... }else if(action.type == ADDSERVER){ return { "v" : state.v + action.result } } return state; }
若是使用fetch,要安裝babel插件:babel-plugin-transform-runtime
babel把async await翻譯成瀏覽器都不認識的語句了,因此要用插件解決,不讓babel翻譯:
const path = require('path'); module.exports = { entry : "./www/app/main", // 入口 output: { path: path.resolve(__dirname, "www/dist"), // 出口文件夾 filename: "bundle.js" // 出口文件名 }, watch : true, // 自動檢測變化,進行打包 module: { // 配置一些插件 rules: [ { test: /\.js$/, // 全部.js結尾的文件 loader: "babel-loader", // 都要使用babel-loader處理 include: [path.resolve(__dirname, "www/app")], exclude: [path.resolve(__dirname, "node_modules")], options: { presets: ["env" , "react"], plugins: ["transform-object-rest-spread", "transform-runtime" ] } } ] } }
如今講解重要知識,如何從服務器上請求默認數據,此時要經過:
app.js服務端出數據接口:
var a = 0; var b = 0; var c = 0; app.get("/api2" , (req,res)=>{ res.json({ "result": [ { value: a, name: '清晰' }, { value: b, name: '通常' }, { value: c, name: '懵逼' } ] }); }); //投票接口 app.get("/toupiao/:zimu" , (req,res)=>{ var zimu = req.params.zimu; if(zimu == "a") a++; if(zimu == "b") b++; if(zimu == "c") c++; res.json({ "result": [ { value: a, name: '清晰' }, { value: b, name: '通常' }, { value: c, name: '懵逼' } ] }); }); app.listen(3000);
containers/App.js
import React from 'react'; import {connect} from "react-redux"; import Counter from "../components/counter/index.js"; import Pie from "../components/pie/index.js"; export default class App extends React.Component { constructor(props) { super(props); } render() { return ( <div> <Counter></Counter> <Pie></Pie> </div> ); } }
components/pie/index.js組件的構造函數中調用函數:
import React from 'react'; import {connect} from "react-redux"; import {bindActionCreators} from "redux"; import * as pieActions from "../../actions/pieActions.js"; class Pie extends React.Component { constructor(props) { super(props); ///組件還沒上樹時,發異步請求數據 props.pieActions.loadServer(); } //組件已經上樹,而後初始化echart結構 componentDidMount(){ this.pic = echarts.init(this.refs.pic); } //React開發中沒有回調函數的,因此數據回來了,在組件將要更新的生命週期寫 //組件將要更新,爲何會將要更新,由於reducer中的result變了! //爲何它變了,由於fetch回來了,從而發出dispatch,影響result了。 componentWillUpdate(nextProps){ //這是百度的圖表插件標準寫法,就是一個配置,最重要的是最後一項data,來自服務器 var option = { tooltip: { trigger: 'item', formatter: "{a} <br/>{b}: {c} ({d}%)" }, legend: { orient: 'vertical', x: 'left', data: ['清晰', '通常', '懵逼'] }, series: [ { name: '懵逼指數', type: 'pie', radius: ['50%', '70%'], avoidLabelOverlap: false, label: { normal: { show: false, position: 'center' }, emphasis: { show: true, textStyle: { fontSize: '30', fontWeight: 'bold' } } }, labelLine: { normal: { show: false } }, //這裏呈遞數據 data: nextProps.result } ] }; //設置option,組件就能顯示圖表了 this.pic.setOption(option); } render() { return ( <div> <p>結果:{this.props.result}</p> <div ref="pic" style={{"width":"300px" ,"height":"300px"}}></div> <button onClick={()=>{this.props.pieActions.toupiao('a')}}>清晰</button> <button onClick={()=>{this.props.pieActions.toupiao('b')}}>通常</button> <button onClick={()=>{this.props.pieActions.toupiao('c')}}>懵逼</button> </div> ); } } export default connect( ({pie})=>({ result: pie.result }), (dipatch)=>({ pieActions: bindActionCreators(pieActions, dipatch) }) )(Pie);
actions/pieActions.js中寫異步請求數據
export const loadServer = () => async (dispatch , getState) => { //異步請求數據 const {result} = await fetch("/api2").then(data=>data.json()); //存儲到reducer dispatch({"type" : "LOADSERVER" , result}); } export const toupiao = (zimu) => async (dispatch, getState) => { const { result } = await fetch("/toupiao/" + zimu).then(data => data.json()); dispatch({ "type": "LOADSERVER", result }); }
reducers/pie.js要處理action的工做
export default (state = {"result" : []} , action) => {
if(action.type == "LOADSERVER"){
return {"result" : action.result};
}
return state;
}
reducers/index.js
import {combineReducers} from "redux"; import counter from "./counter.js"; import pie from "./pie.js"; //暴露合併的reducer export default combineReducers({ counter , pie });