jectProps}>{child}</Panel>;
} else {
panelChild = cloneElement(child, injectProps);
}
index++;
return panelChild;
});
};
准确的说,这是一个 HOC,我们将代理、翻译传给 Filter 的影响或者 panel 面板需要使用的 props 传递给 Panel 面板。比如 onChange 回调,或者面板隐藏的回调以及当前哪一个 panel 需要展开等。
由于 Panel 的面板复杂度我们未知。为了避免不断的展开和收齐不必要的 render,我们采用 transform
的方式,将面板不需要显示的面板移除屏幕外,需要展示的在移入到屏幕内部。具体可见 Panel 的render return
return (
<View
ref={r => {
this.refPanelContainer = r;
}}
style={[
defaultStyle.panel,
styles.panel,
this.panelContainerStyle,
{
transform: `translateX(-${this.containerTransformDes})`,
opacity: 0,
},
]}>
<View
ref="mask"
style={[
defaultStyle.mask,
styles.mask,
showStyle,
isWeb ? { top: 0, zIndex: -1 } : { top: 0 },
]}
onClick={this.handleMaskClick}
onTouchMove={this.handleMaskTouchMove}
/>
{cloneElement(child, injectProps)}
</View>
);
注意: Panel 面板的坑远不止这些,比如,我们都知道,render 是最消耗页面性能的,而页面初始化进来,面板名没有展示出来(此时面板 Panel 在屏幕外),那么是否需要走 Panel 面板的 render 呢?但是目前的这种写法,Panel 组件的生命周期是会都走到的。但是如果遇到 Panel 里面需要请求数据,然后页面 url 里查询参数有 locationId=123
,navItem 需要展示对应的地理位置.如果不渲染 Panel 如何根据 id 拿到对应的地名传递给 navItem 去展示?对,我们可以拦截 Panel 面板的 render 方法,让 Panel render null,然后别的生命周期照样运行。但是,如果 render 中用户有对 ref
的使用,那么就可能会造成难以排查的 bug。
所以最终,为了提高页面的可交互率但是又不影响页面需求的情况下,我们提供了一个可选的工具:Performance HOC 。 注意,是可选。
export default function performance(Comp) {
return class Performance extends Comp {
static displayName = `Performance(${Comp.displayName})`;
render() {
const { shouldInitialRender } = this.props.panelAttributes;
if (shouldInitialRender) {
return super.render();
} else {
return <View />;
}
}
};
}
通过配置Panel 的 shouldInitialRender 属性来告诉我,是否第一次进来,拦截 render。
当然,Panel 也有很多别的坑,比如,现在 Panel 为了重复 render,将 Panel 移除屏幕外,那么,动画从上而下展开设置初始动画闪屏如何处理?
Filter 的代码就是初始化、format、检查校验各种传参,以及 Panel 和 NavBar 通信中转 比如 format、比如 handleNavbarPress
NavBar 核心代码
NavBar 架构
核心代码
从架构图中大概可以看出,NavBar 中通过不同的配置,展示不同的 NavBarItem 的类型,NavQuickSearch
,NavRelatePanel
这里需要注意的是: NavBar 的数据是通过 Filter
props 传入的,如果状态放到 Filter 也就是 NavBar 的父组件管理的话,会导致 Panel 组件不必要的渲染(虽然已经提供 Panel 层的 shouldComponentUpdate 的配置参数),同时也是为了组件设计的高内聚、低耦合,我们将传入的 props 封装到 NavBar 的 state 中,自己管理状态。
constructor(props) {
super(props);
const navConfig = formatNavConfig(props.navConfig);
this.state = {
navConfig,
};
}
// 这里我们提供内部的 formatNavConfig 方法,具体内容根据不同组件业务需求不同代码逻辑不同,这里就不展开说明了
NavBar 中还需要注意的就是被动更新:Panel 层点击后,NavBar 上文字的更新,因为这里我们利用父组件来进行 Panel 和 NavBar 的通信
//Filter.js 调用 NavBar 的方法
/**
* 更新 Navbar 文案
*/
handleNavTextChange = (index, navText, isChange = true) => {
// Navbar 的 render 抽离到内部处理,可以减少一次 Filter.Panel 的额外 render
this.asyncTask(() => {
this.refNavbar.updateOptions(index, navText, isChange);
});
};
//NavBar.js 提供给 Filter.js 调用的 updateOptions
/**
* 更新 navConfig,Filter 组件调用
* 异步 setState 规避 rax 框架 bug: 用户在 componentDidMount 函数中调用中 this.props.onChange 回调
* 重现Code:https://jsplayground.taobao.org/raxplayground/cefec50a-dfe5-4e77-a29a-af2bbfcfcda3
* @param index
* @param text
* @param isChange
*/
updateOptions = (index, text, isChange = true) => {
setTimeout(() => {
const { navConfig } = this.state;
this.setState({
navConfig: navConfig.map((item, i) => {
if (index === i) {
return {
...item,
text,
isChange,
};
}
return item;
}),
});
}, 0);
};
最后 NavBar 中的 item 分为 快速搜索和带有 panel 的 NavBarItem两种,但是对于其公共功能,比如渲染