笔记本键盘 发疯_如何使用ImmutableJS而不会发疯

笔记本键盘 发疯

如果您在Javascript生态系统中,则可能会知道有很多新库,并且仍在开发良好实践,尤其是在ReactJS生态系统中。

如果您正在使用ReactJS开发单页应用程序,并且正在使用Redux进行状态管理 ,那么您可能听说过ImmutableJS。

它是一个库,可帮助开发人员保持其状态不变,从而避免难以发现的错误。

不仅如此,它还有助于React在适当的时间重新渲染组件。 您可能知道React使用浅层比较来了解某些道具是否已更改。

当您将结构化数据传递到组件(数组,对象)时,这可能会带来问题。 React不会执行深度比较(因为它很昂贵),因此您可能会遇到意想不到的行为。

// In reducer.js
const state = {
usersPage: {
loading: false,
isFetched: false,
list: [],
},
companiesPage: {
loading: false,
isFetched: false,
list: [],
},
};
// In UsersPage.js
class UsersPage extends React.Component {
render() {
const { loading, list } = this.props.usersPage;
    return (
<div>
{ loading && <LoadingIndicator /> }
{ !loading && <UsersTable list={list} /> }
</div>
);
}
}
function mapStateToProps(state) {
return {
usersPage: selectors.selectUsersPage(state) ,
};
}
// In selectors.js
const selectUsersPage = (state) => state.usersPage;

考虑上面的代码,一切似乎都很好。 但是,如果您以错误的方式实施状态更新,例如 您不会创建新对象,React不会更新您的组件。 例如:

// Incorrect implementation
function reducer(state, action) {
switch(action.type) {
case 'USERS_LOADED':
state.usersPage.list = action.payload;
state.usersPage.loading = false;
state.usersPage.isFetched = true;
return state;
default:
return state;
}
}
// Correct implementation
function reducer(state, action) {
switch(action.type) {
case 'USERS_LOADED':
return {
...state,
usersPage: Object.assign({}, state.usersPage, {
list: Object.payload,
loading: false,
isFetched: true,
}),
};
default:
return state;
}
}

正如您在上面的代码中看到的那样,事情很容易变得棘手。 您必须确保在保留状态的其他分支并正确更新当前分支中的变量的同时返回新对象。

现在,让我们看一下ImmutableJS的实现:

import { fromJS } from 'immutable';
const state = fromJS({
usersPage: {
loading: false,
isFetched: false,
list: [],
},
companiesPage: {
loading: false,
isFetched: false,
list: [],
},
});
function reducer(state, action) {
switch(action.type) {
case 'USERS_LOADED':
return state
.setIn(['usersPage', 'list'], fromJS(action.payload))
.setIn(['usersPage', 'isFetched'], true)
.setIn(['usersPage', 'loading'], false)
;
default:
return state;
}
}

这种语法使事情变得更易于阅读和维护。 每当更改状态时,ImmutableJS都会返回一个新对象。

但是,您将必须使用ImmutableJS getter来获取值:

// In UsersPage.js
class UsersPage extends React.Component {
render() {
const { usersPage } = this.props;
const loading = usersPage.get('loading');
const list = usersPage.get('list').toJS();
    return (
<div>
{ loading && <LoadingIndicator /> }
{ !loading && <UsersTable list={list} /> }
</div>
);
}
}
function mapStateToProps(state) {
return {
usersPage: selectors.selectUsersPage(state),
};
}
// In selectors.js
const selectUsersPage = (state) => state.get('usersPage');

更糟糕的是,有时ImmutableJS对象会更深地泄漏到您的组件中,甚至泄漏到您的呈现组件中(请toJS() )。 这通常是人们反对使用ImmutableJS的原因之一。

当您计划发布软件包时,情况甚至更糟。 您不想让开发人员使用某些库来束缚使用它的开发人员的双手。

那么如何结合两全其美呢?

重新选择救援!

Reselect是一个很棒的库,可以帮助您解决此问题。

让我们首先了解Reselect的用途:

用于Redux的简单“选择器”库。

  • 选择器可以计算派生数据,从而允许Redux存储最小可能状态。
  • 选择器是有效的。 除非更改了其中一个参数,否则不会重新计算选择器。
  • 选择器是可组合的。 它们可以用作其他选择器的输入。

和我裸一下。 考虑以下代码:

import { createSelector } from 'reselect';
const selectUsersPageDomain = (state) => state.get('usersPage');
const makeSelectUsersPage = createSelector(
selectUsersPageDomain,
(substate) => substate.toJS(),
);

然后,您可以再次使用纯JS语法:

import { createStructuredSelector } from 'reselect';
class UsersPage extends React.Component {
render() {
const { loading, list } = this.props.usersPage;
return (
<div>
{ loading && <LoadingIndicator /> }
{ !loading && <UsersTable list={list} /> }
</div>
);
}
}
const mapStateToProps = createStructuredSelector ({
usersPage: selectors.makeSelectUsersPage(),
});

如果userPage分支中没有任何变化,则选择器不会重新计算其值,React 也不会触发组件渲染。

如您所见,使用此策略,您可以将两个方面的优点结合起来。

但是有一件重要的事情,我没有谈论过,并没有明显的好处:

使用ImmutableJS更新某些分支时,所有未触及的分支均保持不变:

const { fromJS } = require('immutable');
const state = fromJS({
usersPage: {
loading: false,
isFetched: false,
list: [],
},
companiesPage: {
loading: false,
isFetched: false,
list: [],
},
});
console.log('Updating usersPage branch');
const state1 = state.setIn(['usersPage', 'loading'], true);
state.get('usersPage') === state1.get('usersPage'); // false
state.get('companiesPage') === state1.get('companiesPage'); // true
state === state1; // false
console.log('Updating companiesPage branch');
const state2 = state1.setIn(['companiesPage', 'loading'], true);
state1.get('usersPage') === state2.get('usersPage'); // true
state1.get('companiesPage') === state2.get('companiesPage'); // false
state1 === state2; // false

注意:我删除了 console.log 语句以缩短行数。

如果您查看代码,将会看到未更新的分支保持不变->因此, reselect选择选择器将不会触发更新。 仅更新分支的选择器将被更新

但是状态的根也被更新了! 因此,在编写选择器时必须小心一点:

// In reducer.js
const { fromJS } = require('immutable');
const state = fromJS({
pages: { // We put our pages 1 more level deeper
usersPage: {
loading: false,
isFetched: false,
list: [],
},
companiesPage: {
loading: false,
isFetched: false,
list: [],
},
}
});
// In selectors.js
// Incorrect implementation
import { createSelector } from 'reselect';
const selectPagesDomain = (state) => state.get('pages');
const selectUsersPageDomain = (state) => state.get('usersPage');
const selectCompaniesPageDomain = (state) => state.get('companiesPage');
const makeSelectUsersPage = createSelector(
selectPagesDomain , // source of problem
selectUsersPageDomain,
(substate) => substate.toJS(),
);
const makeSelectCompaniesPage = createSelector(
selectPagesDomain , // source of problem
selectCompaniesPageDomain,
(substate) => substate.toJS(),
);

如果您以这种方式实现选择器,则仅在其中之一中更改值时,都会同时触发makeSelectCompaniesPagemakeSelectUsersPage 。 这可能是您不想要的东西。

他们为什么被触发? 回顾以下规则:

  • 选择器是有效的。 除非更改了其中一个参数,否则不会重新计算选择器。

由于selectPagesDomain如果分支的任何一个已更改,将返回不同的值,因此reselect每次都会重新计算两个选择器。

这是正确的实现:

// Correct implementation
import { createSelector } from 'reselect';
const selectUsersPageDomain = (state) => state.getIn(['pages', 'usersPage']) ;
const selectCompaniesPageDomain = (state) => state.getIn(['pages', 'companiesPage']) ;
const makeSelectUsersPage = createSelector(
selectUsersPageDomain ,
(substate) => substate.toJS(),
);
const makeSelectCompaniesPage = createSelector(
selectCompaniesPageDomain ,
(substate) => substate.toJS(),
);

您希望选择最接近要在选择器中使用的数据的路径,以防止在更改其他分支时重新计算。

如果您成功到这里,恭喜!

让我们放在一起:

 // In reducer.js
const { fromJS } = require('immutable');
const state = fromJS({
pages: {
usersPage: {
loading: false,
isFetched: false,
list: [],
},
companiesPage: {
loading: false,
isFetched: false,
list: [],
},
}
});
function reducer(state, action) {
switch(action.type) {
case 'USERS_LOADED':
return state
.setIn(['usersPage', 'list'], fromJS(action.payload))
.setIn(['usersPage', 'isFetched'], true)
.setIn(['usersPage', 'loading'], false)
;
case 'COMPANIES_LOADED':
return state
.setIn(['companiesPage', 'list'], fromJS(action.payload))
.setIn(['companiesPage', 'isFetched'], true)
.setIn(['companiesPage', 'loading'], false)
;
default:
return state;
}
}
export default reducer;
 // In selectors.js
import { createSelector } from 'reselect';
const selectUsersPageDomain = (state) => state.getIn(['pages', 'usersPage']);
const selectCompaniesPageDomain = (state) => state.getIn(['pages', 'companiesPage']);
export const makeSelectUsersPage = createSelector(
selectUsersPageDomain,
(substate) => substate.toJS(),
);
export const makeSelectCompaniesPage = createSelector(
selectCompaniesPageDomain,
(substate) => substate.toJS(),
);
 // In UsersPage.js
import { createStructuredSelector } from 'reselect';
import * as selectors from './selectors';
class UsersPage extends React.Component {
render() {
const { loading, list } = this.props.usersPage;
    return (
<div>
{ loading && <LoadingIndicator /> }
{ !loading && <UsersTable list={list} /> }
</div>
);
}
}
const mapStateToProps = createStructuredSelector({
usersPage: selectors.makeSelectUsersPage(),
});
export default connect(mapStateToProps)(UsersPage);
 // In CompaniesPage.js
import { createStructuredSelector } from 'reselect';
import * as selectors from './selectors';
class CompaniesPage extends React.Component {
render() {
const { loading, list } = this.props.companiesPage;
    return (
<div>
{ loading && <LoadingIndicator /> }
{ !loading && <CompaniesTable list={list} /> }
</div>
);
}
}
const mapStateToProps = createStructuredSelector({
companiesPage: selectors.makeSelectCompaniesPage(),
});
export default connect(mapStateToProps)(CompaniesPage);

而已!

现在,您可以使用ImmutableJS的所有优点,而无需在容器或呈现组件中使用其getter。

最重要的是,您获得了重新选择库的优势,这可以帮助您提高选择器的效率。

让我知道你的想法!

参考文献:

翻译自: https://hackernoon.com/how-to-use-immutablejs-without-going-crazy-bfcb805a0043

笔记本键盘 发疯

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值