简单快捷的在react中处理state数据

331 阅读3分钟

在react开发过程中,大家应该对state的处理感受颇深。就算使用了hooks进行开发,如果数据层级较多,分成多个useState使用起来会显得很累赘,并且代码量巨大。但是使用一个state的时候,数据层级过深又会导致一个不小心由于引用关系视图不能进行更新。

虽然react官方已经有了useReducer来在某些场景替代useState,但是开发使用redux的时候依然会有类似的烦恼,于是就有了react-addons-update

(来自react官方的示例)

const initialState = {count: 0};

function reducer(state, action) {
  switch (action.type) {
    case 'increment':
      return {count: state.count + 1};
    case 'decrement':
      return {count: state.count - 1};
    default:
      throw new Error();
  }
}

function Counter() {
  const [state, dispatch] = useReducer(reducer, initialState);
  return (
    <>
      Count: {state.count}
      <button onClick={() => dispatch({type: 'decrement'})}>-</button>
      <button onClick={() => dispatch({type: 'increment'})}>+</button>
    </>
  );
}

不过react-addons-update已经被弃用,随之用来取代的是immutability-helper。两个库大体差距并不大,但是后者会多一个很重要的扩展功能,这也是immutability-helper更加强大的地方。(这里我贴上immutability-helper库地址,同时由于这个库之前没有特别好的翻译,这里是本人翻译的文档地址)

import update, { extend } from 'immutability-helper';

extend('$addtax', function(tax, original) {
  return original + (tax * original);
});
const state = { price: 123 };
const withTax = update(state, {
  price: {$addtax: 0.8},
});
assert(JSON.stringify(withTax) === JSON.stringify({ price: 221.4 }));

于是,我们就可以用immutability-helper来改写我们原本看起来很繁琐的state

我们在这里用一个比较直观易懂的例子,我们来写一个只通过redux处理的登录注册逻辑。

首先我们确定一下我们数据结构, 大体是这个样子:

const database = {
	loginUser: { // 这里是登录用户信息,id和用户名
		userId: number,
		name: string
	},
	userInfo: [ // 这里是所有用户信息,也就是相当于用户表
		{
			id: number,
			username: string,
			password: string
		},
        	...
	]
}

于是,不使用immutability-helperreducer就可以写成这个亚子:

const user = (
    // 初始state
    state = { 
    	loginUser: {
            userId: -1, // 当无用户登录时,默认id为-1
            name: '' // 当无用户登录时,默认用户名为空
        },
            userInfo: [] // 没有用户注册时,userInfo为空
        }
    },
    action
) => {
	switch(action.type) {
	    case 'LOGIN': // 登录action
        	return { // 纯函数原则,返回新的state
            	...state, // 展开state,保留其他数据
                loginUser: action.loginUser // 通过action拿到登录信息
            };
        case 'REGISTER':
        	return {
            	...state,
                userInfo: [
                	...state.userInfo,
                    action.userInfo
                ]
            };
        case 'CHANGE_USER_INFO':
        	return {
            	...state,
                userInfo: userInfo.map(item => { // 通过map方法来修改数据
                	if (item.id === action.id) {
                    	return {
                        	...item,
                            ...action.changeUserInfo
                        }
                    } else {
                    	return item
                    }
                })
            };
  	    default:
    		return state;
    }
};

此处就只用这么三个小实例来说明,可以发现,在typeLOGIN的时候其复杂程度还不算很高,但当数据层级逐渐变深的时候,就可以发现需要每一次都展开数据保留其完整性,而且还需要一层一层向下查找,引用一句immutability-helper文档中的原话:

This is not only annoying, but also provides a large surface area for bugs. 他不仅很烦人,同时还为出现bug提供了大面积的代码。

因此,我们是时候来正式看看immutability-helper到底为我们做了什么了。

由于初始化等与不使用的时候并无差别,所以我们索性这次拆开分别看几种type并只看返回的部分。

// LOGIN
update(state, { // update会直接返回纯新引用的数据
      loginUser: {
          userId: {
              $set: action.userId
          },
          name: {
              $set: action.name
          }
      }
      /* 
        当然这种其实也是可以的
        loginUser: {
          $set: action.loginUser
        } 
      */
  })

可以看到对于这种层级比较浅的数据使用起来并无太大差距,可以说只是为我们省去了展开state这么一个操作。

那我们继续看REGISTER

update(state, {
   userInfo: {
    	$push: [
            action.userInfo
        ]
    }
})

很明显,在REGISTER这里就已经很明显要简洁的多了,我们省去了展开state,还省去了展开state.userInfo。只需要一个简单的$push,就可以直接将数据加进去,如果这还不够爽,那我们继续看CHANGE_USER_INFO

update(state, {
    userInfo: {
        [state.userInfo.findIndex(item => item.id === action.id)]: {
            $set: action.userInfo
        }
    }
})

或者

update(state, {
    userInfo: userInfo => {
        return update(userInfo, {
            [userInfo.findIndex(item => item.id === action.id)]: {
                $set: action.userInfo
            }
        });
    }
})

或者...

你有很多方式可以实现,这里只不过用了两种不太一样的方式来实现。我相信不论从代码可阅读性以及代码量等方面,都已经有了明显的优势。我认为无需再继续举例,通过上面的例子已经足够体现数据层级再深入的时候,写起来是会多么舒爽了。更何况,我这里还没有说明扩展指令的操作,就已经这么舒爽了。

所以,不要再为了处理state而头疼,快来投入更简单更快捷的immutability-helper的怀抱吧(滑稽