(一)React 基础

150 阅读16分钟

一、简介

React 是一个用于构建用户界面的 JAVASCRIPT 库,主要用于构建 UI,很多人认为 React 是 MVC 中的 V,起源于 Facebook 的内部项目,用来架设 Instagram 的网站,并于 2013 年 5 月开源。

参考资料:

  1. React官网资料
  2. 由浅入深快速掌握React Fiber
  3. 受控组件和非受控组件
  4. react 生命周期 & 执行顺序

1、特点

React 拥有较高的性能,代码逻辑非常简单,越来越多的人已开始关注和使用它

    1. 声明式设计 −React采用声明范式,可以轻松描述应用。
    1. 高效 −React通过对DOM的模拟,最大限度地减少与DOM的交互。
    1. 灵活 −React可以与已知的库或框架很好地配合。
    1. JSX − JSX 是 JavaScript 语法的扩展。React 开发不一定使用 JSX ,但我们建议使用它。
    1. 组件 − 通过 React 构建组件,使得代码更加容易得到复用,能够很好的应用在大项目的开发中。
    1. 单向响应的数据流 − React 实现了单向响应的数据流,从而减少了重复代码,这也是它为什么比传统数据绑定更简单。

2、生态

  • 脚手架/框架
    • umi:可插拔的企业级react应用框架
    • create react app:官方支持的创建react单页应用程序的方法
    • Nextjs:ssr框架
    • React-vr:vr框架,适用于展厅、房屋设计
    • Reactxp:多端框架
  • 组件库
    • ant Design系列:pc、mobile,引入Ant Design设计概念
    • Material-UI:实现了谷歌Material Design设计规范,世界流行界面
  • 工具类
    • Redux:遵循函数式编程思想的状态管理插件
    • Mobx:面向对象变迁和响应式编程的状态管理插件
    • Immutable-js:解决javasript Immutable Data的问题
  • 跨端类
    • Remax:阿里的React跨端框架,目前支持支付宝、微信、字节小程序
    • Taro:类React跨端框架,支持主流小程序及React Native
    • React Native:js编写原生的React框架
  • 其他
    • react-window和react-virtualized:虚拟滚动库,提供可服用组件,用于展示列表、网络和表格数据。

二、基础

1、JSX

React 使用 JSX 来替代常规的 JavaScript,JSX 是一个看起来很像 XML 的 JavaScript 语法扩展,其实就是将js和html结合起来书写。

const jsx = <p>hello, jerry</p>;

render() {
    return jsx;
}
  • 合法的JSX元素
    • 普通的dom标签,如div/p/span等
    • 申明的react组件,例如通过class或者函数创建的jsx组件,用户定义的组件必须以大写字母开头
    • false、null、undefined、true为合法元素,不会渲染
    • 字符串(最终会渲染一个text节点)
    • 数字类型,最终会渲染出来
{false && (<p>test</p>)} {/* 渲染空元素 */}
{0 && (<p>true</P>)}   {/* 与门如果出现非布尔值,渲染与预期有出入,渲染出0 */}
{0 ? null : (<p>true</P>)} {/* 建议使用三目运算符 */}
  • 语法
    • {}使用js表达式:三目运算符、执行函数、数据map等
    • class/for这类html属性是关键字,使用className、htmlFor的形式来定义
    • props默认值为true
    • 点语法,一个模块导出多个组件,可以使用点语法直接使用
    • 注释:{/* xxx */}
//点语法
import React from 'react';
const MyComponents = {
  DatePicker: function DatePicker(props) {
    return <div>Imagine a {props.color} datepicker here.</div>;
  }
}
function BlueDatePicker() {
  const props = {firstName: 'Ben', lastName: 'Hector'};
  return <MyComponents.DatePicker color="blue" className="text" {...props}/>;
}

//动态组件
import React from 'react';
import { PhotoStory, VideoStory } from './stories';

const components = {
  photo: PhotoStory,
  video: VideoStory
};

function Story(props) {
  const SpecificStory = components[props.storyType];
  return (
    <SpecificStory story={props.story} onClick={() => console.log("clicked!")}/>
  );
}
  • 语法糖
    • 本质为React.createElement(component, props, ...children)函数的语法糖。
    • babel-plugin-transform-react-jsx插件,可以将 jsx 编译为 react 的内部⽅法createElement。

2、函数组件和class组件

在react中,可以使用 class 形式或是函数的形式来创建一个组件:

//函数类型组件
export function Foo(props) { 
  return (
    <div>{ props.text || 'Foo'}</div>
  ); 
}

//基于类的组件
export class Bar extends React.Component {
  render() {
      return (
	  <div>{this.props.text || 'Bar'}</div>
      );
   } 
}

//引用
import {Foo, Bar} from './components/CompType'

两种组件的区别:

  • 加载的 props ⽅式不同,函数式定义组件从组件函数的参数加载;class 形式的组件通过this.props获取传⼊的参数。
  • 函数式组件⽐较简单,内部⽆法维护状态;class 形式内部可以通过this.statethis.setState⽅法定义和更新内部 state ,同时更新 render ⾥⾯的函数渲染的结果。
  • class 组件内部可以定义更多的⽅法在实例上,但是函数式组件⽆法定义。
  • class组件需要使用new实例化,函数组件直接使用。

3、有/无状态组件

1、有状态组件
  • 特点
    • 是类组件
    • 有继承
    • 可以使用this
    • 可以使用react的生命周期
    • 使用较多,容易频繁触发生命周期钩子函数,影响性能
    • 内部使用 state,维护自身状态的变化,有状态组件根据外部组件传入的 props 和自身的 state进行渲染
  • 使用场景
    • 需要使用到状态的
    • 需要使用状态操作组件的(无状态组件的也可以实现,通过新版本react hooks也可实现)
  • 总结 类组件可以维护自身的状态变量,即组件的 state ,类组件还有不同的生命周期方法,可以让开发者能够在组件的不同阶段(挂载、更新、卸载)对组件做更多的控制。类组件则既可以充当无状态组件,也可以充当有状态组件。当一个类组件不需要管理自身状态时,也可称为无状态组件。
import React, { Component } from "react";
export default class StateTest extends Component {
    state = {
        counter: 1
    };
    
    componentDidMount() {
        // 请不要直接修改状态值
        // this.state.counter += 1;
        
        // 批量执行
        // this.setState(obj, cb)
        // this.setState(fn, cb)
        this.setState(prevState => ({
            counter: prevState.counter + 1
        }));
        this.setState(prevState => ({
            counter: prevState.counter + 1
        }));
        this.setState(prevState => ({
            counter: prevState.counter + 1
        }));
    }
    
    render() {
        return <div>{this.state.counter}</div>
    }
}
2、无状态组件
  • 特点
    • 不依赖自身的状态state
    • 可以是类组件或者函数组件
    • 可以完全避免使用 this 关键字(由于使用的是箭头函数事件无需绑定)
    • 有更高的性能,当不需要使用生命周期钩子时,应该首先使用无状态函数组件
    • 组件内部不维护 state ,只根据外部组件传入的 props 进行渲染的组件,当 props 改变时,组件重新渲染。
  • 使用场景
    • 组件不需要管理 state,纯展示
  • 优点
    • 简化代码、专注于 render
    • 组件不需要被实例化,无生命周期,提升性能。 输出(渲染)只取决于输入(属性),无副作用
    • 视图和数据的解耦分离
  • 缺点
    • 无法使用 ref
    • 无生命周期方法
    • 无法控制组件的重渲染,因为无法使用shouldComponentUpdate 方法,当组件接受到新的属性时则会重渲染
  • 总结 组件内部状态且与外部无关的组件,可以考虑用状态组件,这样状态树就不会过于复杂,易于理解和管理。当一个组件不需要管理自身状态时,也就是无状态组件,应该优先设计为函数组件,比如自定义的 Button、Input等组件。

4、受控组件和非受控组件

  • 受控组件:表单数据是由 React 组件来管理的,推荐使用
  • 非受控组件:表单数据将交由 DOM 节点来处理,可以通过ref获取表单中数据
//受控组件
class Form extends Component {
  constructor() {
    super();
    this.state = {
      name: '',
    };
  }

  handleNameChange = (event) => {
    this.setState({ name: event.target.value });
  };

  render() {
    return (
      <div>
        <input
          type="text"
          value={this.state.name}
          onChange={this.handleNameChange}
        />
      </div>
    );
  }
}

//非受控组件
class Form extends Component {
  handleSubmitClick = () => {
    const name = this._name.value;
    // do something with `name`
  }

  render() {
    return (
      <div>
      	{/* defaultValue为初始值,通过ref可获取input值 */}
        <input type="text" ref={input => this._name = input} defaultValue="Jian"/>
        <button onClick={this.handleSubmitClick}>Sign up</button>
      </div>
    );
  }

5、条件渲染和循环 & 绑定事件

// 实现一个简单的购物车页面,加购商品、加、减商品
import React, { Component } from "react";

export default class CartSample extends Component {
    // 状态初始化一般放在构造器中
    constructor(props) {
        super(props);
        this.state = {
            goods: [
                { id: 1, text: "Mac pro" },
                { id: 2, text: "huawei pro" }
            ],
            text: "",
            cart: []
        };
        this.addGood = this.addGood.bind(this);
    }

    // 回调函数声明为箭头函数
    textChange = e => {
        this.setState({ text: e.target.value });
    };


    addGood() {
        this.setState(prevState => {
            return {
                goods: [
                    ...prevState.goods,
                {
                    id: prevState.goods.length + 1,
                    text: prevState.text
                }]
            };
        });
    }

    // 加购函数
    addToCart = good => {
        // 创建新购物车
        const newCart = [...this.state.cart];
        const item = newCart.find(c => c.id === good.id);

        if (item) {
            item.count = item.count + 1;
        } else {
            newCart.push({ ...good, count: 1 });
        }
        
        // 更新
        this.setState({ cart: newCart });
    };


    // 处理数量更新
    add = good => {
        // 创建新购物车
        const newCart = [...this.state.cart];
        const item = newCart.find(c => c.id === good.id);
        item.count = item.count + 1;

        // 更新
        this.setState({ cart: newCart });
     };

    minus = good => {
        // 创建新购物车
        const newCart = [...this.state.cart];
        const item = newCart.find(c => c.id === good.id);
        item.count = item.count - 1;

        // 更新
        this.setState({ cart: newCart });
    };

    render() {
    // const title = this.props.title ? <h1>this.props.title</h1> : null;

        return (
            <div>
                {/* 条件渲染 */}
                {this.props.title && <h1>{this.props.title}</h1>}

                {/* 列表渲染 */}
                <div>
                    <input
                        type="text"
                        value={this.state.text}
                        onChange={this.textChange}
                    />
                    <button onClick={this.addGood}>添加商品</button>
                </div>

                <ul>
                    {this.state.goods.map(good => (
                        <li key={good.id}>
                            {good.text}
                            <button onClick={() => this.addToCart(good)}>加购</button>
                        </li>
                    ))}
                </ul>

                {/* 购物车 */}
                <Cart data={this.state.cart} minus={this.minus} add={this.add} />
             </div>
            );
        }
    }

    function Cart({ data, minus, add }) {
        return (
            <table>
                <tbody>
                    {data.map(d => (
                        <tr key={d.id}>
                            <td>{d.text}</td>
                            <td>
                            <button onClick={() => minus(d)}>-</button>
                            {d.count}
                            <button onClick={() => add(d)}>+</button>
                            </td>
                        </tr>
                    ))}
                </tbody>
            </table>
        );
    }

6、组件生命周期

class组件中,可申明如下生命周期:

1、16.3 之前的版本

挂载阶段 mount

  • componentWillMount:在新节点插入DOM结构之前触发 (组件挂载到DOM前调用,这里写setState不会引起组件的重新渲染)
  • render:componentWillMount和componentDidMount之间触发 (返回一个react元素,react根据此函数的返回值渲染DOM,不能在这里setState,会引起死循环
  • componentDidMount:在新节点插入DOM结构之后触发 (组件挂载到DOM后调用,且只会调用一次
    • 初始化请求最好在componentDidMount中发送请求,之前是服务器渲染阶段,不适合发送请求;请求中可能有操作dom操作,会出现问题。客户端发送请求api最好放在服务器不会渲染的方法里,在组件挂载至dom元素后再进行请求更新 更新阶段 update
  • componentWillReceiveProps:已加载的组件收到新的参数时调用,只有props参数更新时才会触发
  • shouldComponentUpdate:组件判断是否重新渲染时调用,返回false取消更新组件,不会调用render (在组件的props或者state发生改变时会触发该方法
  • componentWillUpdate:当你的组件再次渲染时,在render方法前调用
  • render
  • componentDidUpdate:在render函数执行完毕,且更新的组件已被同步到DOM后立即调用,该方法不会在初始化渲染时触发 卸载阶段 unmount
  • componentWillUnMount:在组件从DOM中移除时立即触发 加载顺序:
  • 首次加载时:componentWillMount -> render -> componentDidMount
  • 参数变化时:componentWillReceiveProps(props参数变化) -> shouldComponentUpdate -> componentWillUpdate -> render -> componentDidUpdate
  • 组件卸载:componentWillUnMount
import React, { Component } from "react";
export default class Lifecycle extends Component {
    constructor(props) {
        super(props);
        // 常用于初始化状态
        console.log("1.组件构造函数执行");
    }

    componentWillMount() {
        // 此时可以访问状态和属性,可进行api调用等
        console.log("2.组件将要挂载");
    }

    componentDidMount() {
        // 组件已挂载,可进行状态更新操作
        console.log("3.组件已挂载");
    }

    componentWillReceiveProps() {
        // 父组件传递的属性有变化,做相应响应
        console.log("4.将要接收属性传递");
    }

    shouldComponentUpdate() {
        // 组件是否需要更新,需要返回布尔值结果,优化点
        console.log("5.组件是否需要更新?");
        return true;
    }

    componentWillUpdate() {
        // 组件将要更新,可做更新统计
        console.log("6.组件将要更新");
    }

    componentDidUpdate() {
        // 组件更新
        console.log("7.组件已更新");
    }

    componentWillUnmount() {
        // 组件将要卸载, 可做清理工作
        console.log("8.组件将要卸载");
    }

    render() {
        console.log("组件渲染");
        return <div>生命周期探究</div>;
    }
}

2、16.3 之后的版本

react-lifecycle.png 新增:

  • static getDerivedStateFromProps(props, state):在调用 render 方法之前调用,并且在初始挂载及后续更新时都会被调用。它应返回一个对象来更新 state,如果返回 null 则不更新任何内容。条件: state 的值在任何时候都取决于 props
  • getSnapshotBeforeUpdate():在最近一次渲染输出(提交到 DOM 节点)之前调用。它使得组件能在发生更改之前从 DOM 中捕获一些信息(例如,滚动位置)。此生命周期的任何返回值将作为参数传递给 componentDidUpdate()
  • static getDerivedStateFromError(error):会在后代组件抛出错误后被调用 ,它将抛出的错误作为参数,并返回一个值以更新 state
  • componentDidCatch(error, info):在后代组件抛出错误后被调用,用于记录错误之类的情况:

加载顺序:

  • 挂载时:constructor() -> static getDerivedStateFromProps() -> render() -> componentDidMount()
  • 更新时:static getDerivedStateFromProps() -> shouldComponentUpdate() -> render() -> getSnapshotBeforeUpdate() -> componentDidUpdate()
  • 卸载时:componentWillUnmount()
  • 错误处理:当渲染过程,生命周期,或子组件的构造函数中抛出错误时 static getDerivedStateFromError() -> componentDidCatch()

7、事件代理

react中的事件使用事件代理,采用全局单例的event对象,用于跨端、兼容性和性能提升,异步操作最好将对象内部需要的值进行拷贝,否则会导致this指向问题。

import React, {Component} from 'react';
class App extends Component {
  clickHandle1(e){
    setTimeout(function(){
      console.log('button1 click', e.currentTarget.innerText);
    }, 1000)
  }
  clickHandle2(e){
    console.log('button2 click', e.currentTarget.innerText);
  }
  render(){
    return (
      <div className="App">
        {/* click1报错,click2输出button2 */}
        {/* 解决方法:
            clickHandle1(e){
              let text = e.currentTarget.innerText;
              setTimeout(function(){
                console.log('button1 click', text);
              }, 1000)
            }
         */}
        <button onClick={this.clickHandle1}>button1</button>
        <button onClick={this.clickHandle2}>button2</button>
      </div>
    )
  }
}

8、immutable和immer

  1. 函数引用:render函数中,使用箭头函数或者bind向子组件传递函数时,每次会重新创建函数,导致子组件进行渲染,影响性能,建议写入constructor里或外部提取使用箭头函数
class App extends React.Component>{
  constructor(props){
    super(props);
    this.state = {
      num: 1,
      title: "Jian"
    }
    //推荐写法1,在constructor中使用bind
    this.handleClick2 = this.handleClick1.bind(this);
  }
  
  handleClick1(){
    this.setState({
      num: this.state.num + 1
    })
  }
	//推荐写法2,使用箭头函数绑定
  handleClick3 = () => {
    this.setState({
      num: this.state.num + 1
    })
  }
	
  render(){
    return (
      <div>
        {/* 不推荐写法 */}
        <ChildComponent onClick={this.handleClick1.bind(this)}></ChildComponent>
        <ChildComponent onClick={()=> this.handleClick1()}></ChildComponent>

        {/* 考点1解决方法:提取函数 */}
        <ChildComponent onClick={this.handleClick2}></ChildComponent>
        <ChildComponent onClick={this.handleClick3}></ChildComponent>
      </div>
    )
  }
}
  1. 对象引用:传递给子组件对象时,使用深拷贝会引起的对象的引用断裂,等价于每次传入子组件中的对象为新对象,导致子组件重新渲染,可使用immutable库或者immer类库避免,可配合 shouldComponentUpdate 进行追踪,来进⾏性能优化
//immutable库使用
const immutable = require('immutable')
const data = {
  key1: 'test1'key2: 'test2'
}
const a = immutable.fromJS(data);//转换为内置对象,
const b = a.set('key1', 'valueb');
console.log(a.key1 === b.key1);  //false
console.log(a.key2 === b.key2);  //true

//immer库使用
const produce = require('immer')
const state = {
  key1: 'test1'key2: 'test2'
}
const newState = produce(state, (draft) => {
  draft.key1 = 'newKey1';
})
console.log(newState.key1 === state.key1);  //false
console.log(newState.key2 === state.key2);  //true

9、常用API

1、React.creatElement
React.createElement( //创建并返回指定类型的新React元素
  type, 			//标签名字符串,class组件或函数组件,fragment类型
  [props],
  [...children]
)
2、React.Component和React.PureComponent
  • React.Component是使用 ES6 classes方式定义 React 组件的基类,必须有render函数。
  • React.PureComponent 与 React.Component很相似,两者的区别在于React.Component并未实现 shouldComponentUpdate()(,而 React.PureComponent`中以浅层对比 prop 和 state 的方式来实现了该函数。

解析:

PureComponent表示一个纯组件,可以用来优化React程序,减少render函数执行的次数,从而提高组件的性能。

在React中,当prop或者state发生变化时,可以通过在shouldComponentUpdate生命周期函数中执行return false来阻止页面的更新,从而减少不必要的render执行。React.PureComponent会自动执行 shouldComponentUpdate。

不过,pureComponent中的 shouldComponentUpdate() 进行的是浅比较,也就是说如果是引用数据类型的数据,只会比较不是同一个地址,而不会比较这个地址里面的数据是否一致。浅比较会忽略属性和或状态突变情况,其实也就是数据引用指针没有变化,而数据发生改变的时候render是不会执行的。如果需要重新渲染那么就需要重新开辟空间引用数据。PureComponent一般会用在一些纯展示组件上。

使用pureComponent的好处:当组件更新时,如果组件的props或者state都没有改变,render函数就不会触发。省去虚拟DOM的生成和对比过程,达到提升性能的目的。这是因为react自动做了一层浅比较。

3、context

Context 提供了一种在组件之间共享此类值的方式,而不必显式地通过组件树的逐层传递 props

在react-router-dom以及redux中常使用,重点掌握内容

  • React.createContext(defaultValue)
    • 创建一个context
    • 当 React 渲染一个订阅了这个 Context 对象的组件,这个组件会从组件树中离自身最近的那个匹配的 Provider 中读取到当前的 context 值
    • 只有当组件所处的树中没有匹配到Provider时,defaultValue才生效
  • Context.Provider
    • 每个context对象都会返回一个Provider react组件,用于传递数据
    • 每个Provider接收value属性,传递给消费组件
    • 可一对多,一个Provider对应多个消费者;可嵌套,里层覆盖外层数据
    • value值变化,消费组件重新渲染,新旧变化使用Object.is
    • provider及其内部consumer组件不受制于shouldComponentUpdate函数,因此当 consumer 组件在其祖先组件退出更新的情况下也能更新
  • Context.Consumer
    • react组件可以订阅到context变更,用于在函数组件中订阅context
    • 子元素为函数,接收context值,返回React节点,{context => (/* 基于context值渲染 */)}
  • Context.displayName:context对象有一个displayName的属性,类型为字符串,用于devtools显示context的name
  • Class.contextType:挂载在 class 上的 contextType 属性会被重赋值为一个由 React.createContext()创建的 Context 对象。这能让你使用 this.context 来消费最近 Context 上的那个值。你可以在任何生命周期中访问到它,包括 render 函数中
// Context 可以让我们无须明确地传遍每一个组件,就能将值深入传递进组件树。
// 为当前的 theme 创建一个 context(“light”为默认值)。
const ThemeContext = React.createContext('light');
class App extends React.Component {
  render() {
    // 使用一个 Provider 来将当前的 theme 传递给以下的组件树。
    // 无论多深,任何组件都能读取这个值。
    // 在这个例子中,我们将 “dark” 作为当前的值传递下去。
    return (
      <ThemeContext.Provider value="dark">
        <Toolbar />
      </ThemeContext.Provider>
    );
  }
}

// 中间的组件再也不必指明往下传递 theme 了。
function Toolbar() {
  return (
    <div>
      <ThemedButton />
    </div>
  );
}

class ThemedButton extends React.Component {
  // 指定 contextType 读取当前的 theme context。
  // React 会往上找到最近的 theme Provider,然后使用它的值。
  // 在这个例子中,当前的 theme 值为 “dark”。
  // 使用 static 这个类属性来初始化你的 contextType,Class.contextType的api
  static contextType = ThemeContext;
  render() {
    return <Button theme={this.context} />;
  }
}

4、Ref与Ref转发

  • React.createRef():创建ref,通过ref获取dom元素
  • React.forwardRef((props, ref)=>{/* */}):转发ref到子组件
const FancyButton = React.forwardRef((props, ref) => (
  <button ref={ref} className="FancyButton">
    {props.children}
  </button>
));

// 你可以直接获取 DOM button 的 ref:
const ref = React.createRef();
<FancyButton ref={ref}>Click me!</FancyButton>;

5、memo

React.memo为高阶组件,props检查,渲染缓存,浅层比较:如果你的组件在相同 props 的情况下渲染相同的结果,那么你可以通过将其包装在 React.memo 中调用,以此通过记忆组件渲染结果的方式来提高组件的性能表现。这意味着在这种情况下,React 将跳过渲染组件的操作并直接复用最近一次渲染的结果。

注意:React.memo 仅检查 props 变更。如果函数组件被 React.memo 包裹,且其实现中拥有 useStateuseReducer或 useContext的 Hook,当 state 或 context 发生变化时,它仍会重新渲染

//1、使用第一个参数浅层比较
const MyComponent = React.memo(function MyComponent(props) {
  /* 使用 props 渲染 */
});

//2、使用第2个参数可进行深入比较
function MyComponent(props) {
  /* 使用 props 渲染 */
}
function areEqual(prevProps, nextProps) {
  /*
  如果把 nextProps 传入 render 方法的返回结果与
  将 prevProps 传入 render 方法的返回结果一致则返回 true,
  否则返回 false
  */
}
export default React.memo(MyComponent, areEqual);

三、实现一个简单的form 高阶组件

import React from "react";
import { Input, Button } from "antd";

// 创建一个高阶组件:扩展现有表单,事件处理、数据收集、校验
function kFormCreate(Comp) {
    return class extends React.Component {
        constructor(props) {
            super(props);
            this.options = {};
            this.state = {};
        }

        handleChange = e => {
            const { name, value } = e.target;
            console.log(name, value);

            this.setState({ [name]: value }, () => {
                // 确保值发生变化再校验
                this.validateField(name);
            });
        };

        // 单项校验
        validateField = field => {
            // 1. 获取校验规则
            const rules = this.options[field].rules;

            // 任意一项失败则返回false
            const ret = !rules.some(rule => {
                if (rule.required) {
                    if (!this.state[field]) {
                        //校验失败
                        this.setState({
                            [field + "Message"]: rule.message
                        });
                    return true;
                    }
                }
            });

            if (ret) { // 校验成功
                this.setState({
                    [field + "Message"]: ""
                });
            }
            return ret;
        };

        // 校验所有字段
        validate = cb => {
            const rets = Object.keys(this.options).map(field =>
                this.validateField(field)
            );
            const ret = rets.every(v => v == true);
            cb(ret, this.state);
        };

        // 创建input包装器
        getFieldDec = (field, option) => {
            // 保存当前输入项配置
            this.options[field] = option;
            return InputComp => (
                <div>
                    {React.cloneElement(InputComp, {
                        name: field,
                        value: this.state[field] || "",
                        onChange: this.handleChange
                    })}
                    {/* 校验错误信息 */}
                    {this.state[field+'Message'] && (
                        <p style={{color:'red'}}>{this.state[field+'Message']}</p>
                    )}
                </div>
            );
        };

        render() {
            return <Comp getFieldDec={this.getFieldDec} validate={this.validate} />;
        }
    };
}


@kFormCreate
class KForm extends React.Component {
    onSubmit = () => {
        console.log("submit");
        // 校验所有项
        this.props.validate((isValid, data) => {
            if (isValid) {
            //提交登录
            console.log("登录:", data);
            // 后续登录逻辑
            } else {
            alert("校验失败");
            }
        });
    };

    render() {
        const { getFieldDec } = this.props;
        return (
            <div>
                {getFieldDec("uname", {
                rules: [{ required: true, message: "用户名必填" }]
            })(<Input />)}

                {getFieldDec("pwd", {
                rules: [{ required: true, message: "密码必填" }]
                })(<Input type="password" />)}

                <Button onClick={this.onSubmit}>登录</Button>
            </div>
        );
    }
}


export default KForm;