参考 ant 组件库来学习。
https://ant.design/components/icon-cn/
https://www.iconfont.cn/collections/detail?spm=a313x.7781069.1998910419.d9df05512&cid=16957
安装:
npm install prop-types —save
这是一个第三方库,用来检测Component props里属性的类型。
例如,你可以这样去使用它:
当你在Son组件里指定了属性的类型,比如optionalNumber必须为number类型,当从父组件传递的optionalNumber不是number的时候,就会报错,其实也就是给属性类型强类型了。
Son.propTypes = {optionalArray: PropTypes.array,//检测数组类型optionalBool: PropTypes.bool,//检测布尔类型optionalFunc: PropTypes.func,//检测函数(Function类型)optionalNumber: PropTypes.number,//检测数字optionalObject: PropTypes.object,//检测对象optionalString: PropTypes.string,//检测字符串optionalSymbol: PropTypes.symbol,//ES6新增的symbol类型}
Icon组件代码:
static PropTypes用来规范组件,static defaultProps是设置组件属性的默认值。
import React, { Component } from 'react';import '../../font/iconfont';import PropTypes from 'prop-types';class Icon extends Component {// 规范组件static PropTypes = {name: PropTypes.string}static defaultProps = {name: 'ABC'}render(){let {name,...rest} = this.props;return (<div><span {...rest} className={`icon iconfont icon-${name}`}></span>Icon</div>);}}export default Icon;
APP组件中使用Icon:
class App extends Component{render(){return (<div><Icon onClick={() => alert('Hello')} style={{color: 'red'}} name={'Primary'}/></div>);}}
Button组件:
书写样式:
定义两个颜色变量。
$primaryColor: blue;$warningColor: yellow;
遵循BEM规范:—代表状态,__代表子元素。
@import "../theme/index.scss";.react-ui__btn{&--primary{background: $primaryColor;color: #fff;background-color: #1890ff;border-color: #1890ff;text-shadow: 0 -1px 0 rgba(0,0,0,0.12);}&--warning{background: $warningColor;}}
import Component from 'React';import PropTypes from 'prop-types';import Icon from '../Icon'class Button extends Component {static PropTypes = {icon: PropTypes.string,type: PropTypes.string}static defaultProps = {icon: 'submit',type: 'primary'}rednder(){const {icon,children,type,...rest} = this.props;return (<button className={`react-ui__btn--${type}`}><Icon name={icon}/>{children}</button>);}}
受控组件和非受控组件:
受控组件: react 接收控制权, 非react的手段不能对元素进行更改. 每一个操作都在react的监控内;必须有value和onChange。在React中,每当表单的状态发生变化时,都会被写入到组件的state中,这种组件在React被称为受控组件。受控组件中,组件渲染的状态与它的value或者checked相对应。React通过这种方式消除了组件的局部状态。React官方推荐使用受控组件。
受控组件更新state流程:
1. 可以通过在初始state中设置表单的默认值。2. 每当表单的值发生变化时,调用onChange事件处理器。3. 事件处理器通过合成事件对象e拿到改变后的状态,并更新state。4. setState触发视图的重新渲染,完成表单组件值得更新。
非受控组件: 元素的状态不受 react 控制;
简单的说,如果一个表单组件没有value props(单选按钮和复选框对应的是checked props)就可以称为非受控组件。这样,我们可以使用defaultValue和defaultChecked来表示组件的默认状态。
在React中,非受控组件是一种反模式,它的值不受组件自身的state或者props控制,通常需要为其添加ref prop来访问渲染后的底层DOM元素。
在受控组件中,可以将用书输入的内容输出展示,而在受控组件中,如果不绑定onChange事件,我们在文本框中输入任何内容都不会展示。可以看到受控组件和非受控组件的最大区别就是,非受控组件状态并不会受应用状态的控制,应用中也多了局部组件状态,而受控组件的值来源于state。
render(){return (<div>{this.state.name} - {this.state.age}<input value={this.state.name} /><input defaultValue={this.state.name} /></div>);}
上面的例子中:
如果受控组件(第一个input)没有onChange事件的话,用户在输入框里输入值,是输入不了的,显示不了;所以受控组件必须要有value和onchange属性。
render(){return (<div>{this.state.name} - {this.state.age}<input value={this.state.name} onChange={(e) => this.setState({name: e.target.value})}/><input defaultValue={this.state.name} /></div>);}
上面的代码:受控组件(第一个input),当用户输入改变时,并不会引起第二个input输入框里值的变化,因为第二个input是非受控组件,它不受react state的控制,defaultValue给设置了默认值为state里的值,但当state值变化的时候,并不会引起输入框里值的变化。
可以看到受控组件和非受控组件的最大区别就是,非受控组件状态并不会受应用状态的控制,应用中也多了局部组件状态,而受控组件的值来源于state。
性能上的问题:**
在受控组件中,每次表单的值发生变化都会调用一次onChange时间处理器,这会有一些性能消耗,任然不提倡在React中使用非受控组件。
下面代码中,受控组件的变化,会触发组件重绘(会输出Will update);但是非受控组件输入值的改变并不会触发重绘,这导致了组件中出现了局部状态。
class App extends Component{constructor(props){super(props);this.state = {name: 'David',age: 26};console.log(this.state);}value = 'hello';render(){return (<div>{this.state.name} - {this.state.age}<input value={this.state.name} onChange={(e) => this.setState({name: e.target.value})}/><input defaultValue={this.value} onChange={(e) => {this.value = e.target.value;}}/></div>);}componentWillUpdate(){console.log('will update!');}}
Input组件示例代码:
支持非受控组件,和受控组件。
APP:
import React, {Component} from 'react';import ReactDOM from 'react-dom';import * as serviceWorker from './serviceWorker';import Input from './ui/input/input.js';class App extends Component{constructor(props){super(props);this.state = {value: ''};}value = '1';render(){return (<div><Input size='large'/><Input size='middle'/><Input size='small' rule={/^\d*$/} message={'Please input number!'} value={this.state.value} onChange={(e) => this.setState({value: e.target.value})}/><Input size='small' defaultValue={this.value} onChange={ (e) => {this.value = e.target.value;}}/></div>);}componentWillUpdate(){console.log('will update!');}}ReactDOM.render(<App />, document.getElementById('root'));// If you want your app to work offline and load faster, you can change// unregister() to register() below. Note this comes with some pitfalls.// Learn more about service workers: https://bit.ly/CRA-PWAserviceWorker.unregister();
Input组件:
import React, { Component } from 'react';import classNames from 'classnames';import propTypes from 'prop-types';import './input.scss';class Input extends Component {constructor(props){super(props);this.state = {focus: false,innerValue: ''};}static propTypes = {value: propTypes.string,onChange: propTypes.func,size: propTypes.string}static defaultProps = {size: 'middle',onChange: () => {}}get isControl(){return 'value' in this.props;}get value() {if (this.isControl){return this.props.value;}else {return this.state.innerValue;}}render(){const {focus} = this.state;const {children,size,onChange,rule = new RegExp(),message,...rest} = this.props;let cls = classNames({'react-ui__input': true,input: true,focus,[`size-${size}`]: true});return (<div><div className={cls}><inputvalue={this.value}onFocus= {(e) => {this.setState({focus: true});}}onBlur= {(e) => {this.setState({focus: false});}}onChange={(e)=> {if(!this.isControl) {this.setState({innerValue: e.target.value});}this.props.onChange(e);}}/></div><p>{!rule.test(this.value) && message}</p></div>);}componentDidMount() {this.setState({innerValue: this.props.defaultValue});}}export default Input;
Input 样式文件:
@import '../theme/index.scss';.react-ui__input {border: 1px solid #d2d2d2;display: inline-block;input{border: none;outline: none;}&.focus {border-color: blue;}&.size-large input {font-size: 18px;line-height: 40px;}&.size-middle input{font-size: 14px;line-height: 30px;}&.size-small input {font-size: 12px;line-height: 20px;}&--primary{//background: $primaryColor;color: #fff;background-color: #1890ff;border-color: #1890ff;text-shadow: 0 -1px 0 rgba(0,0,0,0.12);}&--warning{background: $warningColor;}}
Table组件:
import React, {Component} from 'react';import ReactDOM from 'react-dom';import * as serviceWorker from './serviceWorker';import Table from './ui/table/table';const dataSource = [{name: 'David',age: 26,gender: 'Male'},{name: 'Lee',age: 20,gender: 'Male'}];const columns = [{title: 'Name',dataIndex: 'name',key: 'name',render: (text, item, index)=>{return (<a href="#">{text}?{index}</a>);}},{title: 'Age',dataIndex: 'age',key: 'age'},{title: 'Gender',dataIndex: 'gender',key: 'gender'}];class App extends Component{constructor(props){super(props);}render() {return (<div><Table columns={columns} dataSource={dataSource}/></div>);}componentWillUpdate(){console.log('will update!');}}ReactDOM.render(<App />, document.getElementById('root'));// If you want your app to work offline and load faster, you can change// unregister() to register() below. Note this comes with some pitfalls.// Learn more about service workers: https://bit.ly/CRA-PWAserviceWorker.unregister();
import React, {Component} from 'react';import PropTypes from 'prop-types';class ColumnItem extends Component {render(){const {title} = this.props.item;return (<td>{title}</td>);}}class Columns extends Component {render(){const {columns} = this.props;return (<thead><tr>{columns.map((item) => {return (<ColumnItem item={item} key={item.key}/>);})}</tr></thead>);}}class DataSourceItem extends Component {render(){const {columns,dataItem,index} = this.props;const tds = columns.map((item) => <td> {item.render ? item.render(dataItem[item.dataIndex], dataItem, index) : dataItem[item.dataIndex]} </td>);return (<tr>{tds}</tr>);}}class DataSource extends Component {render(){const {dataSource,columns} = this.props;let trs = dataSource.map((item, index) => <DataSourceItem dataItem={item} index={index} columns={columns}/>);return (<tbody>{trs}</tbody>);}}class Table extends Component{static propTypes = {columns: PropTypes.array,dataSource: PropTypes.array}static defaultProps = {columns: [],dataSource: []}render(){const {columns,dataSource} = this.props;return (<div><table border="1" cellPadding="0" cellSpacing="0"><Columns columns={columns}/><DataSource dataSource={dataSource} columns={columns}/></table></div>)}}export default Table;
