Immutable.js结合React的使用

3,010 阅读5分钟

immutable.js介绍

immutable.js 是 Facebook 开源的一个项目,用于实现 javascript 的数据不可变,解决引用带来的副作用。

不变的数据(Immutable Data )一旦创建就无法更改,从而可以简化应用程序开发,进行防御性复制,并可以使用简单的逻辑实现高级的备忘和更改检测技术。持久数据提供了一个可变API,该API不会就地更新数据,而是始终产生新的更新数据,也就是说,对 Immutable 对象的任何修改或添加删除操作都会返回一个新的 Immutable 对象。

// 原来的写法
let foo = {a: {b: 1}};
let bar = foo;
bar.a.b = 2;
console.log(foo.a.b);  // 打印 2
console.log(foo === bar);  //  打印 true

// 使用 immutable.js 后
import Immutable from 'immutable';
foo = Immutable.fromJS({a: {b: 1}});
bar = foo.setIn(['a', 'b'], 2);   // 使用 setIn 赋值
console.log(foo.getIn(['a', 'b']));  // 使用 getIn 取值,打印 1
console.log(foo === bar);  //  打印 false

immutable.js几种数据类型

immutable.js提供了一些永久不可变数据结构,包括:

  • List:有序可重复的列表,类似js中的Array。
  • Stack: 有序列表,通过单链表实现,支持使用unshift()和shift()添加和删除。
  • Map: 无序键值对,类似js中的Object,es6中也有map。
  • OrderedMap:有序的map,根据数据的set()进行排序。
  • Set:没有顺序不能重复的列表。
  • OrderedSet:有序的set,根据数据的add进行排序。
  • Record: 跟Immuable的Map很像,一旦构造好,就不能添加更多的属性(key)

常用API

fromJS()

作用:将一个js数据转换为Immutable类型的数据
用法:fromJS(value, reviver)
简介:value是要转变的数据,reviver是要做的操作。第二个参数可不填,默认情况会将数组准换为List类型,将对象转换为Map类型,其余不做操作。

	let bar = { a: 10, b: [10, 20, 30],c: 40,d: 10, e: [10, 20, 30],f: {g:10,h:{i:20}}}
	let demo = Immutable.fromJS(bar, 
		function (key, value, path) {
		  console.log(Immutable.isKeyed(value),'key='+key,'value='+value,'path=',path)
		})
    	//打印结果
    	//false "key=b" "value=Seq [ 10, 20, 30 ]" "path=" ["b"]
        //false "key=e" "value=Seq [ 10, 20, 30 ]" "path=" ["e"]
        //true "key=h" "value=Seq { "i": 20 }" "path=" (2) ["f", "h"]
        //true "key=f" "value=Seq { "g": 10, "h": undefined }" "path=" ["f"]
        //true "key=" "value=Seq { "a": 10, "b": undefined, "c": 40, "d": 10, "e": undefined, "f": undefined }" "path=" []

toJS()

作用:将一个Immutable List类型数据转换为JS类型的数据
用法:value.toJS()

is()

作用:对两个对象进行比较 用法:is(first,second) 简介:可以比较原始类型(如字符串和数字),Immutable.js集合(如Map和List),以及任何通过提供和方法实现的自定义对象,比较0和-0值是相同的。和js中对象的比较不同,在js中比较两个对象比较的是地址,但是在Immutable中比较的是这个对象hashCode和valueOf,只要两个对象的hashCode相等,值就是相同的,避免了深度遍历,提高了性能。

merge()、mergrWith()、mergeIn()、mergeDeep()、mergeDeepIn()、mergrDeepWith()、

作用:数据的合并,新数据与旧数据对比

此示例为Map结构,List与Map原理相同:

 const Map1 = Immutable.fromJS({a:111,b:222,c:{d:333,e:444}});
 const Map2 = Immutable.fromJS({a:111,b:222,c:{e:444,f:555}});
 const Map3 = Map1.merge(Map2); //旧数据中不存在的属性直接添加,就数据中已存在的属性用新数据中的覆盖
  //Map {a:111,b:222,c:{e:444,f:555}}
 const Map4 = Map1.mergeDeep(Map2); //深合并,新旧数据中同时存在的的属性为新旧数据合并之后的数据
  //Map {a:111,b:222,c:{d:333,e:444,f:555}}
 const Map5 = Map1.mergeWith((oldData,newData,key)=>{ //自定义浅合并,可自行设置某些属性的值
      if(key === 'a'){
        return 666;
      }else{
        return newData
      }
    },Map2);
  //Map {a:666,b:222,c:{e:444,f:555}}

get() 、 getIn()

作用:获取数据结构中的数据

has() 、 hasIn()

作用:判断是否存在某一个key

includes()

作用:判断是否存在某一个value

  • 使用 Immutable.fromJS 而不是 Immutable.MapImmutable.List 来创建对象,这样可以避免 Immutable 和原生对象间的混用,例如,Map & Immutable.Map。
  • Immutable 中的 Map 和 List 虽对应原生 Object 和 Array,但操作非常不同,比如你要用 map.get('key') 而不是 map.keyarray.get(0) 而不是 array[0]。另外 Immutable 每次修改都会返回新对象,也很容易忘记赋值。

immutable.js结合react的项目应用

安装redux-immutable的命令 npm install redux-immutable。
安装immutable的命令 npm install immutable。 redux-immutable提供一个combineReducers()函数,将stroe中最外层的reducer中的state转化为immutable对象(这里涉及到reducer的拆分,拆分用到了与redux中同名的combineReducers()方法)

比如现在我创建了一个Header的组件(代码有删减)

import React,{Component} from 'react';
import {connect} from 'react-redux';
import {CSSTransition} from 'react-transition-group';
class Header extends Component{
    render() {
        const {home,focused,list,login,handleInputFocus,handleInputBlur,handleHomeActive,logout} = this.props
        return (
            <HeaderWrapper>
                
                    <SearchWrapper>
                        <CSSTransition
                            in={focused}
                            timeout={200}
                            classNames="slide"
                        >
                            <NavSearch
                                className={focused?'focused':''}
                                onFocus={()=>handleInputFocus(list)}
                            />
                        </CSSTransition>
                        {this.getListArea()}
                    </SearchWrapper>
            </HeaderWrapper>
        )
    }
    getListArea() {
        const {focused,list,page,totalPage} = this.props;
        const newList = list.toJS()   //immutable对象装换成js对象
        const pageList = []
        //ajax请求发送获取数据之后再显示,保证key值得有效
        if(newList.length){
            for(let i=(page-1)*10;i<page*10;i++) {
                pageList.push(
                    <SearchInfoItem key={newList[i]}>{newList[i]}</SearchInfoItem>
                )
            }
        }
        if(focused) { //鼠标聚焦输入框时展示热门搜索的列表
            return (
                <SearchInfo>
                    <SearchInfoTitle>
                        热门搜索
                    </SearchInfoTitle>
                    <SearchInfoList>
                        {pageList}
                    </SearchInfoList>
                </SearchInfo>
            )
        }else{
            return null
        }
    }
}

const mapState = (state)=>{
    return {
        home:state.getIn(['header','home']),  //获取state中保存的数据
        focused:state.getIn(['header','focused']),
        list:state.getIn(['header','list']),
    }
}
const mapDispatch = (dispatch)=>{
    return {
        handleHomeActive(value){
            dispatch(changeHomeActive(value))
        },
        handleInputFocus(list) {
            (list.size===0) && dispatch(actionCreators.getList())
            dispatch(actionCreators.searchFocus())
        },
        
    }
}

export default connect(mapState,mapDispatch)(Header)
}

Header组件目录下的reducer.js的内容

import * as constants from './constants'
import {fromJS} from 'immutable'
const defaultState = fromJS({ //将数据转化成immutable数据
    home:true,
    focused:false,
    mouseIn:false,
    list:[],
    page:1,
    totalPage:1
})
export default(state=defaultState,action)=>{
    switch(action.type){
        case constants.SEARCH_FOCUS:
            return state.set('focused',true) //更改immutable数据
        case constants.CHANGE_HOME_ACTIVE:
            return state.set('home',action.value)
        case constants.SEARCH_BLUR:
            return state.set('focused',false)
        case constants.CHANGE_LIST:
            // return state.set('list',action.data).set('totalPage',action.totalPage)
            //merge效率更高,执行一次改变多个数据
            return state.merge({
                list:action.data,
                totalPage:action.totalPage
            })
        case constants.MOUSE_ENTER:
            return state.set('mouseIn',true)
        case constants.MOUSE_LEAVE:
            return state.set('mouseIn',false)
        case constants.CHANGE_PAGE:
            return state.set('page',action.page)
        default:
            return state
    }
}

根目录下的reducer.js文件

import {combineReducers} from 'redux-immutable'
import {reducer as headerReducer} from '../common/Header/store'
//当项目中有多个组件时,reducer可以拆分为多个reducer,然后在最外层使用combineReducers将各个reducer组合在一起。这样代码逻辑比较清晰
const reducer = combineReducers({
    header:headerReducer,
})

export default reducer

引用的文章:

官方文档

腾讯云文档

Immutable 详解及 React 中实践