参考 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-PWA
serviceWorker.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}>
<input
value={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-PWA
serviceWorker.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;