Skip to content

Latest commit

 

History

History
584 lines (495 loc) · 19.2 KB

README.react.md

File metadata and controls

584 lines (495 loc) · 19.2 KB

DOMHtml for React

React的插件,使用对象替代JSX来管理React元素。

功能

  • 使用对象替代JSX,简洁而优雅。

使用方法:

  • 引入ReactDOMHtml

    • 使用NPM安装:

      • npm install react-extensions-dom
      • import { reactDOM, rDOM } from react-extensions-dom;
      • 直接引入模块:

        • 将react-extensions-dom-module.js复制到您的项目目录。

        • import { reactDOM, rDOM } from './react-extensions-dom-module';
      • 不使用模块,直接从HTML引入:

        • <script src="react.extensions.dom.min.js"></script>

调用和参数

  • reactDOMHtml(dom_tag, dom_attr, dom_html, dom_html_after)或rDOM(dom_tag, dom_attr, dom_html, dom_html_after),返回React.createElement对象。

    • return reactDOMHtml(`div`,
          {
              id:`div`,
              class:`div`,
              style:{
                  backgroundColor:`#000`
              }
          },
          `This is a DIV.`
      );
      //<div id="div" className="div" style={backgroundColor:`#000`}>This is a DIV.</div>
  • 在dom_attr中,html字段的优先级大于dom_html参数。

    • return reactDOMHtml(`div`,
          {
              id:`div`,
              class:`div`,style:{
                  backgroundColor:`#000`
              },
              html:`This is a DIV.`
          },`This is a DIV 111.`);
      //<div id="div" className="div" style={{backgroundColor:`#000`}}>This is a DIV.</div>
  • dom_tag参数为对象时,将使用对象中的tag、attr、html构建,dom_attr、dom_html、dom_html_after参数将被忽略。

    • return reactDOMHtml({
          tag:`div`,attr:{
              id:`div1`,class:`div1`
          },html:`This is DIV1.`
      });
      //<div id="div1" className="div1">This is a DIV1.</div>
  • dom_tag参数为对象时,可省略attr,在同一对象层中写tag、attribute、html,更加简洁易读,因此推荐这种写法。

    • 注意:单层对象写法中,tag、attr为保留字。如果需要使用tag、attr,须更换成tagName、attrName(大小写敏感)。

    • 如果需要使用tagName、attrName,须写成tag_name、attr_name,会被自动转换。

    • return reactDOMHtml({
          tag:`div`,
          tag_name:`tagName`,
          id:`div1`,class:`div1`,
          html:`This is DIV1.`
      });
      //<div id="div1" tagName="tagName" className="div1">This is a DIV1.</div>
  • dom_tag或tag为字符串时,为HTML标签。tag为Function时,为React组件,使用方法和JSX保持一致。

    • return reactDOMHtml({
          tag:Component1,id:`com1`,class:`com1`,html:`Component1`
      });
      //<Component1 id="com1" className="com1">Component1</Component1>
  • dom_attr为字符串时,将返回html字符串。

    • return reactDOMHtml(`div`,`This is a DIV.`);
      //<div>This is a DIV.</div>
  • props使用attr对象传递,也可以使用单层对象。

  • html、html_after(对象中为htmlAfter)为特殊的children,仅支持字符串。children支持字符串、对象、数组等任意类型,与JSX保持一致。

    • html永远在children之前,html_after永远在children之后。
  • dom_tag参数为数组时,将返回JSX中类似「<><Component1></Component1><Component2></Component2>...</>」的结构,每个元素的结构和上述对象保持一致,dom_attr、dom_html、dom_html_after参数将被忽略。

    • return reactDOMHtml([
          {tag:`div`,id:`div1`,class:`div1`,html:`This is DIV1.`},
          {tag:`div`,id:`div2`,class:`div2`,html:`This is DIV2.`},
          {tag:`div`,id:`div3`,class:`div3`,html:`This is DIV3.`},
      ]);
      /*
      <>
          <div id="div1" className="div1">This is DIV1.</div>
          <div id="div2" className="div2">This is DIV2.</div>
          <div id="div3" className="div3">This is DIV3.</div>
      </>
      */
  • 可使用class、for替代className、htmlFor。它们会被自动转换为className、htmlFor,因此在传递prop时必须使用className、htmlFor。

使用数组或对象传递class

  • 使用数组传递class

    • 数组中的class会按顺序依次添加到元素的class中。

    • return reactDOMHtml({
          tag:`div`,id:`div`,
          class:[`div1`,`div2`],
          html:`This is a DIV.`
      });
      //<div id="div" className="div1 div2">This is a DIV.</div>
  • 使用对象传递class

    • class对象中key对应的value为true时,才会将此key添加到元素的class中。

    • return reactDOMHtml({
          tag:`div`,id:`div`,
          class:{
              div1:true, 
              div2:false, 
              div3:true
          },html:`This is a DIV.`
      });
      //<div id="div" className="div1 div3">This is a DIV.</div>

双向绑定

  • 使用model传递state和onChange的handle函数,从而更加方便地实现双向绑定。

    • model为数组时,model[0]为value,model[model.length-1]为handle函数,model[1]~model[model.length-1]为扩展数据(如radio的默认选中值)。一般为[value, onChange]格式。

    • model为对象时,将在dom_attr中解构,因此其内部字段与dom_attr保持一致。

    • 在model中,checkbox的checked属性将被自动映射为model[0]:boolean。

    • 组件使用model时,需要接收value、onChange两个props,如果有扩展数据,则按顺序写入extend prop中。

      •   function ModelRoot(){
                const radioList=[
                  `test1`,`test2`,`test3`,
              ];
              const [input, setInput]=useState(``);
              const [text, setText]=useState(``);
              const [check, setCheck]=useState(false);
              const [radio, setRadio]=useState(radioList[1]);
          
                function inputChange(e){
                  setInput(e.target.value);
              }
                function textChange(e){
                  setText(e.target.value);
              }
                function checkChange(e){
                  setCheck(e.target.checked);
              }
                function changeRadio(e){
                  setRadio(e.target.value);
              }
                return rDOM([
                  {tag:`input`, model:[input, inputChange]},
                  {tag:`textarea`, model:[text, textChange]},
                  {tag:`input`, type:`checkbox`, model:[check, checkChange]},
                    {tag:`input`, type:`checkbox`, checked:check, onChange:checkChange},
                    // 以上两个checkbox项的意义完全一致。
                    ...radioList.map((r,i)=>({tag:`input`, type:`radio`, model:[r, r==radio, changeRadio], title:r})),
                    ...radioList.map((r,i)=>({tag:`input`, type:`radio`, model:{value:r, checked:r==radio, onChange:changeRadio}, title:r})),
                    ...radioList.map((r,i)=>({tag:`input`, type:`radio`, model:[r, changeRadio], checked:r==radio, title:r})),
                    ...radioList.map((r,i)=>({tag:`input`, type:`radio`, value:r, onChange:changeRadio, checked:r==radio, title:r})),
                  // 以上四个radio项的意义完全一致。
                  {tag:ModelComponent, model:[input, extend1, extend2, inputChange]},
              ]);
          }
          function ModelComponent({value, onChange, extend}){
              // 组件相关代码
          	//上述组件传入的props:value=input, onChange=inputChange, extend=[extend1, extend2]
          }
      
      

CSS样式

  • 使用JSX标准的CSS格式。

  • return reactDOMHtml({
        tag:`div`,
        id:`div`,
        class:[`div`,`div2`],
        style:{
            backgrundColor:`#FFF`,
            opacity:0,
        },
        html:`This is a DIV.`
    });
    //<div id="div" className="div div2" style={{backgrundColor:`#FFF`,opacity:0,}}>This is a DIV.</div>

子元素

  • 可以在一个元素中直接插入多个子元素,并且支持多层子元素。

  • 如果children为对象,则为单一子元素。如果children为数组,则为多个子元素。

  • 子元素的tag、attr、html分别对应dom_tag、dom_attr、dom_html,同样支持上述单层对象写法。

  • 如果没有为子元素指定key,则默认传入index作为key。

    • return reactDOMHtml({
          tag:`div`,
          id:`div`,
          class:[`div`,`div2`],
          html:`This is a DIV.`
          children:[
              {
                  tag:`div`,
                  id:`div_child_1`,
                  class:[`div`, `div_child`],
                  html:`This is a child DIV.`,
                  children:{
                      tag:`div`,
                      id:`div_grandson`,
                      class:[`div`,`div_child`,`div_grandson`],
                      html:`This is a grandson DIV.`,
                  },
              },
              {
                  tag:`div`,
                  attr:{
                      id:`div_child_2`,class:[`div`,`div_child`],
                      html:`This is a child DIV.`
                  },
              }
          ],
      });
      /*
      <div id="div" className="div div2">
          This is a DIV.
          <div id="div_child_1" className="div div_child">
              This is a child DIV.
              <div id="div_grandson" className="div div_child div_grandson">
                  This is a grandson DIV.
              </div>
          </div>
          <div id="div_child_2" className="div div_child">
              This is a child DIV.
          </div>
      </div>
      */

表格元素

  • 表格元素拥有特殊的语法,顶层使用tbody或tr取代children,tr中使用td取代children,并且可省略tag。

  • 表格只建议使用单层对象写法。

  • 可用tbody替代tr。

    • return reactDOMHtml({
          tag:`table`,
          id:`testTable`,class:`testTable`,tr:[
              {id:`tr1`,class:`tr1`,td:[
                  {id:`td1`,class:`td1`,html:`test td 1`},
                  {id:`td2`,class:`td2`,html:`test td 2`},
                  {html:`test td 3`},
                  `test td 4`,
              ]},
              {td:[
                  {id:`td1`,class:`td1`,html:`test td 31`},
                  {id:`td2`,class:`td2`,html:`test td 32`},
                  {html:`test td 33`},
                  `test td 34`,
              ]},
          ],
      });
      /*
      <table id="testTable" className="testTable">
          <tbody>
              <tr id="tr1" className="tr1">
                  <td id="td1" className="td1">test td 1</td>
                  <td id="td2" className="td2">test td 2</td>
                  <td>test td 3</td>
                  <td>test td 4</td>
              </tr>
              <tr>
                  <td id="td1" className="td1">test td 31</td>
                  <td id="td2" className="td2">test td 32</td>
                  <td>test td 33</td>
                  <td>test td 34</td>
              </tr>
          </tbody>
      </table>
      */
  • 注意:td内的元素隐含tag为td,因此不可使用其他tag取代。td内元素应写为如下形式:

    • return reactDOMHtml({
          tag:`table`,
          id:`testTable`,class:`testTable`,tr:[
              {id:`tr1`,class:`tr1`,td:[
                  {id:`td1`,class:`td1`,children:[
                      {tag:`div`,id:`tdiv1`,class:`tdiv1`,html:`test td div`},
                ]},
                  {html:`test td 2`},
                  {html:`test td 3`},
                  `test td 4`,
              ]},
              {td:[
                  {id:`td1`,class:`td1`,html:`test td 31`},
                  {id:`td2`,class:`td2`,html:`test td 32`},
                  {html:`test td 33`},
                  `test td 34`,
              ]},
          ],
      });
      /*
      <table id="testTable" className="testTable">
          <tbody>
              <tr id="tr1" className="tr1">
                  <td id="td1" clasclassNames="td1">
                      <div id="tdiv1" className="tdiv1">test td div</div>
                  </td>
                  <td>test td 2</td>
                  <td>test td 3</td>
                  <td>test td 4</td>
              </tr>
              <tr>
                  <td id="td1" className="td1">test td 31</td>
                  <td id="td2" className="td2">test td 32</td>
                  <td>test td 33</td>
                  <td>test td 34</td>
              </tr>
          </tbody>
      </table>
      */

写法和示例

  • function Table({index}){
        return rDOM({tag:`table`,id:`table${index}`,class:`table table${index}`,tr:[
            {id:`tr`,class:`tr1`,td:[
                `This is Table ${index}, tr 1, td 1`,
                {id:`td2`,html:`This is tr 1, td 2`},
                {id:`td3`,html:`This is tr 1, td 3`},
            ]},
            {id:`tr2`,class:`tr2`,td:[
                `This is Table ${index}, tr 2, td 1`,
                {id:`td2`,html:`This is tr 2, td 2`},
                {id:`td3`,html:`This is tr 2, td 3`},
            ]},
        ]});
    }
    
    function Test({id,className,children}){
        const [count,setCount]=React.useState(0);
        function handleClick(){
            setCount(count+1);
        }
        return rDOM([
            {tag:`h3`,id:id, class:className, html:`This is Test123, h3, ${children}`},
            {tag:`h4`,id:id, class:className, html:`This is Test123, h4, ${children}`},
            {tag:`h5`,id:id, class:className, html:children},
            {tag:`button`,id:id, class:className, html:`${children} count: ${count}`, onClick:handleClick},
            {tag:Test2,html:`This is ${children}, but controled by button. The count is: ${count}`},
        ]);
    }
    
    function Test2(props){
        const [input,setInput]=React.useState(props.children);
        function handleChange(val){
            setInput(val);
        }
        return rDOM([
            {tag:`h3`,...props},
            {tag:`h4`,...props},
            {tag:`h5`,...props},
            {tag:`input`,value:input,onChange:e=>handleChange(e.target.value)},
            {tag:`div`,html:`Input content: ${input}`},
        ]);
    }
    
    const root=ReactDOM.createRoot(document.getElementById('app'));
    root.render(rDOM({tag:`div`,id:`test`,class:`test`,children:[
        {tag:`h1`,key:1,html:`Test1`},
        {tag:`h2`,key:2,html:`Test2`,},
        {tag:`br`},
        {tag:`span`,html:`Test33333333333333333333`},
        {tag:`br`},
        {tag:`span`,html:`Test44444444444444444444`},
        {tag:`hr`},
        {tag:`div`,id:`div1`,class:`div div1`,html:`This is Div1.`,children:[
            {tag:`div`,id:`div1.1`,class:`div div1.1`,html:`This is Div1.1.`,style:{backgroundColor:`#FF3366`}},
            {tag:`div`,id:`div1.2`,class:`div div1.2`,html:`This is Div1.2.`},
            {tag:`div`,id:`div1.3`,class:`div div1.3`,html:`This is Div1.3.`},
            {tag:`button`,id:`button1.4`,class:`button button1.4`,html:`This is Button1.4.`,onClick:e=>console.log(e)},
        ]},
        ...(()=>{
            let tableList=[];
            for(let i=0; i<10; i++){
                tableList.push({tag:Table,index:i});
            }
            return tableList;
        })(),
        {tag:Test,id:`Test123`,class:`Test123`,html:`Test 123456`},
        {tag:Test,id:`Test123`,class:`Test123`,html:`Test 1234567890`},
        {tag:Test2,id:`Test123`,class:`Test123`,html:`Test 789000`},
    ]}));

React官方文档中井字棋游戏的转制示例

index.js
import React, { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import { rDOM } from 'react-extensions-dom';
import "./styles.css";

import App from "./App";

const root = createRoot(document.getElementById("root"));
root.render(rDOM(StrictMode,{children:[
    {tag:App},
]}));
App.js
import { useState } from 'react';
import { Component } from 'react';
import { rDOM } from 'react-extensions-dom';

function Square({value,onSquareClick}){
    return rDOM(`button`,{class:`square`,html:value,onClick:onSquareClick});
}

export default function Game(){
    const [history, setHistory] = useState([Array(9).fill(null)]);
    const [currentMove, setCurrentMove] = useState(0);
    const xIsNext = currentMove % 2 === 0;
    const currentSquares = history[currentMove];

    function handlePlay(nextSquares){
        const nextHistory = [...history.slice(0, currentMove + 1), nextSquares];
        setHistory(nextHistory);
        setCurrentMove(nextHistory.length - 1);
    }

    function jumpTo(nextMove){
        setCurrentMove(nextMove);
    }

    const moves=history.map((squares, move)=>{
        let description;
        if (move > 0) {
            description = `Go to move #${move}`;
        } else {
            description = `Go to game start`;
        }
        return rDOM({tag:`li`,key:move,children:{tag:`button`,onClick:()=>jumpTo(move),html:description}});
    });

    return rDOM({tag:`div`,class:`game`,children:[
        {tag:`div`,class:`game-board`,children:[
            {tag:Board,xIsNext:xIsNext,squares:currentSquares,onPlay:handlePlay},
        ]},
        {tag:`div`,class:`game-info`,children:[
            {tag:`ol`,children:moves},
        ]},
    ]});
}

function Board({xIsNext, squares, onPlay}) {
    function handleClick(i){
        if(squares[i] || calculateWinner(squares)) return;

        const nextSquares = squares.slice();
        if(xIsNext){
            nextSquares[i] = `X`;
        }else{
            nextSquares[i] = `O`;
        }
        onPlay(nextSquares);
    }
    const winner = calculateWinner(squares);
    let status;
    if (winner) {
        status = 'Winner: ' + winner;
    } else {
        status = 'Next player: ' + (xIsNext ? 'X' : 'O');
    }
    return rDOM([
        {tag:`div`,class:`status`,html:status},
        {tag:`div`,class:`board-row`,children:[
            {tag:Square,value:squares[0],onSquareClick:()=>handleClick(0)},
            {tag:Square,value:squares[1],onSquareClick:()=>handleClick(1)},
            {tag:Square,value:squares[2],onSquareClick:()=>handleClick(2)},
        ]},
        {tag:`div`,class:`board-row`,children:[
            {tag:Square,value:squares[3],onSquareClick:()=>handleClick(3)},
            {tag:Square,value:squares[4],onSquareClick:()=>handleClick(4)},
            {tag:Square,value:squares[5],onSquareClick:()=>handleClick(5)},
        ]},
        {tag:`div`,class:`board-row`,children:[
            {tag:Square,value:squares[6],onSquareClick:()=>handleClick(6)},
            {tag:Square,value:squares[7],onSquareClick:()=>handleClick(7)},
            {tag:Square,value:squares[8],onSquareClick:()=>handleClick(8)},
        ]},
    ]);
}

function calculateWinner(squares) {
    const lines = [
        [0, 1, 2],
        [3, 4, 5],
        [6, 7, 8],
        [0, 3, 6],
        [1, 4, 7],
        [2, 5, 8],
        [0, 4, 8],
        [2, 4, 6]
    ];
    for (let i = 0; i < lines.length; i++) {
        const [a, b, c] = lines[i];
        if (squares[a] && squares[a] === squares[b] && squares[a] === squares[c]) {
            return squares[a];
        }
    }
    return null;
}