前言
写过angularjs
的应该对ng-if
, ng-show
, ng-repeat
等内置指令比较熟悉, 在react
里面一般都是用js
来实现这些功能, 但是这种组件化(指令化)的书写方式比较简单易懂和便于维护, 比一个接一个的if
,map
,&&
,||
嵌套简洁得多, 所以可以自己实现几个指令, 方便使用. 需要更多的时候也可以自己再扩展.
ng-if
ng-if
可以说是很简单的指令了, 当if
条件为真时, 展示组件, 否则不展示.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| const NgCmd = ({ if: _if = true, children } = props) => { if (!_if || !children) return null; return children; }; NgCmd.propTypes = { if: React.PropTypes.any, };
ReactDOM.render( <div> <NgCmd if={true}> <div>this is true</div> <span>true</span> </NgCmd> <NgCmd if={false}> <div>this is false</div> <span>false</span> </NgCmd> </div>, document.querySelector('#container'), );
|
注意, 该指令在react
16 以上版本可用, 因为render
函数可以返回数组. 低版本不可以, 需要修改返回值.
ng-show
ng-show
和ng-hide
还有ng-class
可以放在一起说, 因为它们本质上都是改变元素的class
. 在react
里面不需要特殊的支持, 可以使用classnames
插件, 比ng-class
更方便. 具体使用方法可以参考文档.
1 2 3 4 5
| <div> <div className={classNames({ hide: this.state.hide })}>ng-hide</div> <div className={classNames({ hide: !this.state.show })}>ng-show</div> <div className={classNames({ class-one: this.state.needClassOne })}>ng-class</div> </div>,
|
ng-cmd
为了提供控制式组件, 实现一个ng-cmd
, 包括ng-if
和ng-show
的功能.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60
| import React from 'react'; import { PropTypes as P } from 'prop-types'; import _ from 'lodash'; const NgCmd = ({ if: _if = true, show = true, hide = false, children } = props) => { if (!_if || !children) return null; let classHideName = ''; if (!show || hide) { classHideName = 'hide'; } return React.Children.map(children, (child, index) => { if (_.isString(child) || _.isNumber(child)) { return classHideName ? null : child; } if (!React.isValidElement(child)) { throw `ng-cmd only has React Element`; } let childProps = child.props; let className = ''; if (child.props.className && classHideName) { className = child.props.className + ' ' + classHideName; } else if (child.props.className || classHideName) { className = child.props.className || classHideName; } return React.cloneElement(child, { ...childProps, key: index, className: className, }); }); }; NgCmd.propTypes = { hide: P.any, show: P.any, if: P.any, };
const Test = () => { return ( <div> <NgCmd show={false}> <div>show - false</div> </NgCmd> <NgCmd show={true}> <div>show - true</div> </NgCmd> <NgCmd hide={false}> <div>hide - false</div> </NgCmd> <NgCmd hide={true}> <div>hide - true</div> </NgCmd> <NgCmd if={false}> <div>if - false</div> </NgCmd> <NgCmd if={true}> <div>if - true</div> </NgCmd> </div> ); };
|
有个问题需要注意的是, 如果NgCmd
组件的子元素是一个自定义组件, 那么需要自己接收props.className
并使用到顶级节点上, 否则会造成show
和hide
失效. 顺便提供一个ngCmd
装饰器, 用来装饰组件, 即可在组件中直接使用show
,hide
,if
, 不需要使用NgCmd
组件.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138
| function ngCmd(arg) { if (_.isFunction(arg)) { const Wrapper = arg; return class WrapperComponent extends React.Component { render() { return ( <NgCmd {...this.props}> <Wrapper {...this.props} /> </NgCmd> ); } }; } else if (_.isArray(arg)) { let i = 0; let args = []; if (arg.length == 0) { args = ['if', 'show', 'hide']; } else { ['if', 'show', 'hide'].forEach((value, index) => { if (arg.indexOf(value) > -1) { i++; } }); if (i == 0) { throw 'need at least one ng-cmd'; } args = arg; } return (Wrapper) => { return class WrapperComponent extends React.Component { render() { let cmdsObj = {}; ['if', 'show', 'hide'].forEach((v, i) => { if (args.indexOf(v) > -1 && _.has(this.props, v)) { cmdsObj[v] = this.props[v]; } }); return ( <NgCmd {...cmdsObj}> <Wrapper {...this.props} /> </NgCmd> ); } }; }; if (_.has(cmdsObj, 'if') && cmdsObj['if'] == false) { return null; } } } const Test = () => { return ( <div> <NgCmd show={false}> <Test2 /> </NgCmd> <NgCmd show={true}> <Test2 /> </NgCmd> <NgCmd show={false}> <Test3 /> </NgCmd> <NgCmd show={true}> <Test3 /> </NgCmd> <NgCmd show={false}> <Test4 /> </NgCmd> <NgCmd show={true}> <Test4 /> </NgCmd> <NgCmd show={false}> <Test5 /> </NgCmd> <NgCmd show={true}> <Test5 /> </NgCmd> <NgCmd if={false}> <Test2 /> </NgCmd> <NgCmd if={true}> <Test2 /> </NgCmd> <NgCmd if={false}> <Test3 /> </NgCmd> <NgCmd if={true}> <Test3 /> </NgCmd> <NgCmd if={false}> <Test4 /> </NgCmd> <NgCmd if={true}> <Test4 /> </NgCmd> <Test4 if={true} /> <Test4 if={false} /> <Test4 show={true} /> <Test4 show={false} /> <Test4 hide={true} /> <Test4 hide={false} /> <Test5 if={true} /> <Test5 if={false} /> <Test5 show={true} /> <Test5 show={false} /> <Test5 hide={true} /> <Test5 hide={false} /> <Test6 if={true} /> <Test6 if={false} /> <Test6 show={true} /> <Test6 show={false} /> <Test6 hide={true} /> <Test6 hide={false} /> </div> ); }; const Test2 = () => { return 'Test2'; }; const Test3 = (props) => { return <div>Test3</div>; }; @ngCmd class Test4 extends React.Component { render() { return <div className={this.props.className}>Test4-{JSON.stringify(this.props)}</div>; } } @ngCmd(['hide', 'if']) class Test5 extends React.Component { render() { return <div>Test5-{JSON.stringify(this.props)}</div>; } } const Test6 = ngCmd((props) => { return <div className={props.className}>Test6-{JSON.stringify(props)}</div>; });
|
ng-switch
另一个比较有用的控件为ng-switch
, 否则在react
中使用switch
只能使用立即执行函数.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70
| var Test = (props) => { return (<div className="test"> //不使用NgSwitch {(function(){ switch(props.value){ case '1':return <div>one</div>; case '2':return <div>two</div>; case '3':return <div>threee</div>; default : return <div>default</div>; } })()} //使用NgSwitch <NgSwitch when={props} get={(v) => (v.a)}> <NgDefault>default</NgDefault> <NgCase is="1"><div>1</div></NgCase> <NgCase is="2">2</NgCase> <NgCase is="3">3</NgCase> </NgSwitch> </div>) <Test value="1"/> <Test value="2"/> <Test value="3"/> <Test value="4"/>
import React from 'react'; import { PropTypes as P } from 'prop-types'; import _ from 'lodash'; import Immutable from 'immutable';
const NgSwitch = ({ when, get, children, } = props) => { if (_.isNil(when) || _.isNil(children)) { return null; } let child = null; let defaultChild = null; React.Children.forEach(children, function (element) { if (!React.isValidElement(element)) return; if (element.type != NgCase && element.type != NgDefault) return; if (element.type == NgDefault && !defaultChild) { defaultChild = element; return; } else if (element.type == NgDefault && defaultChild) { throw 'ng-switch can not has two default' } if (child == null && element.props.is == (_.isFunction(get) ? get(when) : when)) { child = element; } }); return child || defaultChild; }; NgSwitch.propTypes = { when: P.any.isRequired, get: P.func, }
const NgCase = ({ is, children } = props) => { if (!is) { return null; } return children; }; NgCase.propTypes = { is: P.any.isRequired, }
const NgDefault = ({ children, } = props) => { return children; }; }
|
在NgSwitch
组件中, 可以自定义获取函数get
, 如果使用的是immutable
, 只需要设置get={(v) => (v.get('value'))}
即可.
ng-repeat
ng-repeat
作为angular
里面的一大利器, 使得循环一个dom
结构再也不是很麻烦的事情, 结合ng-if
以及ng-class
, 可以方便的定义重复元素. 这里也仿造ng-repeat
实现一个循环组件, 而且不会有像ng-repeat
造成性能不好的问题, 和原始的map
写法相比性能可能还有提升(有提供测试 demo, 可以自行测试). 同时为了方便项目组使用, 兼容immutable
数据结构和原生数据结构, 提供自定义key
(track by), filter
函数, sortBy
函数. 内置提供$$index
, $$isOdd
, $$total
等相关属性.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155
| import React from 'react'; import { PropTypes as P } from 'prop-types'; import _ from 'lodash'; import Immutable from 'immutable';
const NgFor = ({ for: _for, of: _of = 'item', filter = null, sortBy = null, trackBy = null, render } = props) => { if (!_for || !render) { return; } const _obj = {}; if (Immutable.isCollection(_for)) { if (filter) { _for = _for.filter(filter); } const _size = _for.count(); if (_size == 0) { return null; } return _for .sortBy((v) => v[sortBy]) .map((value, index) => { _obj[_of] = value; const key = Immutable.fromJS(value).get(trackBy) || index; var ele = render({ ..._obj, $$index: index, $$isEnd: index + 1 === _size, $$isStart: index === 0, $$isOdd: index % 2 != 0, $$isEven: index % 2 == 0, $$total: _size, $$key: key, key: key, }); return ele.key ? ele : React.cloneElement(ele, { key }); }); } else { _for = _.filter(_for, filter); const _size = _.size(_for); if (_size == 0) { return null; } return _.map(_.sortBy(_for, sortBy), (value, index) => { _obj[_of] = value; const key = value[trackBy] || index; var ele = render({ ..._obj, $$index: index, $$isEnd: index + 1 === _size, $$isStart: index === 0, $$isOdd: index % 2 != 0, $$isEven: index % 2 == 0, $$total: _size, $$key: key, key: key, }); return ele.key ? ele : React.cloneElement(ele, { key }); }); } }; NgFor.propTypes = { for: P.any.isRequired, of: P.string.isRequired, filter: P.func, sort: P.func, trackBy: P.string, }; NgFor.defaultProps = { of: 'item', };
var Test = (props) => { var start = [ { id: 'a1', value: 'av1' }, { id: 'a2', value: 'av2' }, { id: 'a3', value: 'av3' }, ]; var arrays = []; for (var i = 0; i < 5000; i++) { arrays.push({ id: 'a1', value: 'av1', }); } var arraysIm = Immutable.fromJS(arrays); return ( <div className='test'> {console.log('NgFor no set self key')} {console.time('NgFor no set self key')} <NgFor for={arrays} of='item' render={function (_props) { return ( <div id={_props.item.id}> {_props.item.value} length: {arrays.length} </div> ); }} ></NgFor> {console.timeEnd('NgFor no set self key')} {console.log('NgFor set self key')} {console.time('NgFor set self key')} <NgFor for={arrays} of='item' render={function (_props) { return ( <div id={_props.item.id} key={_props.key}> {_props.item.value} length: {arrays.length} </div> ); }} ></NgFor> {console.timeEnd('NgFor set self key')} {console.log('NgFor Immutable set self key')} {console.time('NgFor Immutable set self key')} <NgFor for={arraysIm} of='item' render={function (_props) { return ( <div id={_props.item.get('id')} key={_props.key}> {_props.item.get('value')} length: {arraysIm.count()} </div> ); }} ></NgFor> {console.timeEnd('NgFor Immutable set self key')} {console.log('js Map set self key')} {console.time('js Map set self key')} {arrays.map((value, index) => { return ( <div id={value.id} key={index}> {value.value} length: {arrays.length} </div> ); })} {console.timeEnd('js Map set self key')} </div> ); }; export { Test };
export default NgFor;
|
性能测试截图(5000):