如何添加新的器件 - ChayCai/Wonton_master GitHub Wiki

器件实现的源代码位于 Wonton/Wonton.CrossUI.Web/ClientApp/src/components/Devices 目录下(以下简称 Devices 目录)。

一般将器件类型分为输入型(如LED)和输出型(如按钮),下面从两个例子介绍如何添加新的器件。

输入型器件:LED

在 Devices 目录下创建 LED 目录,在 LED 目录下又创建 LED.js 和 LEDCore.js 文件。在 LED 目录下引入其他必要的文件。

LEDCore.js

后缀为Core的文件代表这是一个无状态组件,无状态的直观意思是组件中仅涉及 props,不涉及 state。

import React, { Component } from 'react';

import ledOn from './led_on.svg';
import ledOff from './led_off.svg';

export class LEDCore extends Component {
    
    static defaultProps = {
        name: "LED",
        ClassName: "LED",
        onOff: false
    }

    render() {
        return (      
            <div>
                <img src={this.props.onOff ? ledOn : ledOff} alt="led"></img>
            </div>
        );
    }
}

LEDCore 组件的意义十分明了:根据 props.onOff 值改变自身的亮、灭图片,来虚拟展示LED的亮、灭。

LEDCore 组件的 defaultProps 必须设置,name 和 ClassName 必须设为器件名称。

LED.js

没有后缀的文件代表是有状态的组件,状态组件涉及与 FPGA 的通信。

import React, { Component } from 'react';
import { manager } from '../../Service/FPGAManager';
import { LEDCore } from './LEDCore';


export class LED extends Component {
    
    static defaultProps = {
        name: 'LED',
        input: [0], //LED只有一个输入
        ports: ['输入1'],
        portsDirs: ['输入']
    }

    state = {
        inputs: [0]
    }

    componentDidMount() {
        let ins = this.props.instance;

        let that = this;

        manager.Subscribe(ins, this.props.ports, (inputs, deltaTime) => {
            that.setState({
                inputs: inputs
            });
        });

        manager.RegisterProjectPorts(this.props.instance, this.state.inputs.length);
    }

    componentWillUnmount() {
        manager.UnSubscribe(this.props.instance);
        manager.UnRegisterProjectPorts(this.props.instance);
    }

    render() {

        let on = this.state.inputs[0] === 1 ? true : false;

        return (      
            <LEDCore onOff={on}/>
        );
    }
}

首先设置 defaultProps,name 设置为器件名。input 是一个数组,数组的个数是这个器件的输入端口数,这里只有一个LED,因此input 数组个数为1;数组的值表示这个器件的初始值。ports 是一个数组,对 input 数组补充了每个端口的名称信息;portsDirs 是一个数组,对 input 数组补充了每个端口的输入、输出信息。

然后设置 state,本器件发生变化的就是输入端口的值,因此这里使用一个 input 数组,与 defaultProps 中的 input 数组相呼应。

注册两个 React 生命周期函数:componentDidMount(器件加载时触发)和 componentWillUnmount(器件销毁时触发)。 在 componentDidMount 中调用 FPGAManager 的两个注册函数 Subscribe 和 RegisterProjectPorts。在 componentWillUnmount 中调用 FPGAManager 的两个反注册函数 UnSubscribe 和 UnRegisterProjectPorts。

Subscribe 函数主要向 FPGAManager 注册回调函数,即当 FPGAManager 从 FPGA 硬件上获取到数据后能够更新器件。deltaTime 指的是这次 FPGA 运行时钟上升沿距离上一次时钟上升沿过去的时间,单位毫秒。

最后注册 render 函数,这一部分主要调用 LEDCore 这个无状态组件,将从 FPGAManager 取得的数据应用到器件上。

至此一个器件完成构建。

输出型器件:HButton

在 Devices 目录下创建 HButton 目录,在 HButton 目录下又创建 HButton.js 和 HButtonCore.js 文件。在 HButton 目录下引入其他必要的文件。

HButtonCore.js

import React, { Component } from 'react';
import { Button } from 'reactstrap'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faMusic } from '@fortawesome/free-solid-svg-icons'

import './HButton.css';

export class HButtonCore extends Component {
    
    static defaultProps = {
        name: "按钮",
        ClassName: "HButton"
    }

    onCheckClick = (e) => {
        if(this.props.onClick)
        {
            this.props.onClick(e);
        }
    }

    render() {
        return (      
            <Button outline active={this.props.active} size='lg' className="myToggleButton" onClick={e => this.onCheckClick(e)}>
                <FontAwesomeIcon icon={faMusic}/>
            </Button> 
        );
    }
}

HButtonCore 组件在内部实例化了一个 Button,该 Button 使用回调函数将按钮事件传导至上层组件。defaultProps 的要求同 LEDCore。

HButton.js

import React, { Component } from 'react';
import { manager } from '../../Service/FPGAManager';
import { HButtonCore } from './HButtonCore'

import './HButton.css'

export class HButton extends Component {
    
    outputPorts = ['输出1']

    static defaultProps = {
        name: '按钮',
        ports: ['输出1'],
        portsDirs: ['输出']
    }

    state = {
        outputs: [0]
    }

    componentDidMount() {
        manager.Register(this.props.instance, this.state.outputs.length);
        manager.RegisterProjectPorts(this.props.instance, this.state.outputs.length);
    }

    componentWillUnmount() {
        manager.UnRegister(this.props.instance);
        manager.UnRegisterProjectPorts(this.props.instance);
    }

    ButtonClick = (event) => {
        this.setState((prevState) => {
            let nextOutput = 1 - prevState.outputs[0];
            console.log("Button click "+this.props.instance + ": "+nextOutput)
            return {
                outputs : [nextOutput],
            }
        });
        
        manager.UpdateInput(this.props.instance, [1 - this.state.outputs[0]]);
    }

    render() {
        return (
            <HButtonCore onClick={this.ButtonClick} active={this.state.outputs[0] === 1} />
        );
    }
}

输入型器件和输出型器件的代码差异只有对 FPGAManager 的注册方式的差异。在 componentDidMount 中调用 Register 和 RegisterProjectPorts 函数,在 componentWillUnmount 调用反注册函数。

输出型器件还涉及对输出值的改变,调用 FPGAManager 的 UpdateInput 函数即可。注意 UpdateInput 的第二个参数为数组,因为输出型器件可能有多个输出。

器件全局注册

当器件的代码写好后,还不能展示到界面上,还需要在 Devices 目录下的 Devices.js 进行全局注册。

import { LED } from "./LED/LED";
import { LEDCore } from "./LED/LEDCore";
import { HButton } from "./HButton/HButton";
import { HButtonCore } from "./HButton/HButtonCore";

export const deviceMap = new Map([
    ['LED', [LED, LEDCore]],
    ['HButton', [HButton, HButtonCore]]
])

export class Devices {
    constructor (className, opts) {
        return new deviceMap[className][0](opts);
    }
}

export class DeviceCore {
    constructor (className, opts) {
        return new deviceMap[className][1](opts);
    }
}

导入组件类,然后按照上述示例代码扩充 deviceMap 变量即可(Map的值是一个数组,第一个值为有状态类,第二个值为无状态值)。请勿修改其他函数和类代码。

⚠️ **GitHub.com Fallback** ⚠️