taro-designer可視化ドラッグの技術点整理
18003 ワード
いきなり可視化ドラッグの風がフロントエンドの隅々で吹いているようで、自分でも煽ってみましたが、コードはほぼ開発済みで、整理してみました.
githubプロジェクトアドレス:taro-designer
オンライン体験アドレス:taro-desiger
主な技術点は以下の通りである.バックグラウンド テクノロジスタック ドラッグ パッケージ データ構造 エディタ 単一コンポーネント動作 taroのソースコード を生成する.ソースコード のプレビューとダウンロード
会社の一部の業務はインタラクティブな開発をしています.例えば、サイン、贈り物の両替などです.インタラクティブなビジネスは迅速な反復が必要であり、H 5、微信ウィジェット、淘宝ウィジェットをサポートする必要があるため、フロントエンドはtaroをベースフレームワークとして多端なニーズを満たすために採用している.そこで,基礎となるコンポーネントを可視化してドラッグ&ドロップし,ページレイアウトを直接生成し,開発効率を向上させるかどうかを考える.
プロジェクトの様々な限界に直面して、taro 2を採用した.xライブラリ、およびtaroが持参したコンポーネントライブラリ、taro-uiではありません.taroがサポートするプロパティはバラツキがあるため、ビジネス側と議論した後、tarojsコンポーネントライブラリがサポートするh 5と微信ウィジェットの交差を取ってプロパティ編集を行います.
react、mobx、cloud-react、tarojs
左側の選択可能なコンポーネントからエディタに要素をドラッグ&ドロップし、エディタで2回のドラッグ&ドロップソートを行い、ドラッグ位置エラーを解決し、再ドラッグの問題を削除する必要があります.
ドラッグ&ドロップのベースライブラリとしてreact-dndを採用し,具体的な使い方はプロジェクトの実践と文章の説明が単独であり,ここでは後述しない.
プロジェクトコード:react-dnd-nested
demoアドレス:react-dnd-nested-demo
ここで包装されているのはtaroのコンポーネントであり、他のサードパーティのコンポーネントであってもよい.各コンポーネントは、
コードは人間よりも効率的で、正確で、信頼できると信じています.
コンポーネントテンプレートスクリプトの生成
各コンポーネントはtaro対応のコンポーネントをパッケージしているので、
スクリプトの実行
出力
すべてのコンポーネントの対外出力を
スクリプトの実行
ページのインタラクティブデータは、
簡単なページデータの例は次のとおりです.
実現構想:
1、初期化で取得した値が空の場合、デフォルトデータは次のとおりです.
2.
3、左側からコンポーネントをドラッグしてエディタに入り、ドラッグした親コンポーネントidを見つけ、
4、エディタにコンポーネントをドラッグし、moveでコンポーネントを新しい親コンポーネントの下に移動するドラッグしているコンポーネントとその親コンポーネントを見つけ、ターゲットコンポーネントとその親コンポーネント を見つけます.は、ターゲットコンポーネントが配置可能なタイプのコンポーネントであるかどうかを判断する.はい、ターゲットコンポーネントに直接プッシュします.そうでなければ、現在親アセンブリにある を挿入する.ターゲットコンポーネントの親コンポーネントから現在のコンポーネント を除去する.
5、コンポーネントをクリックすると、右側のエディタ領域に
6、ワークスペースを空にし、二次確認を追加して誤操作を防止し、ページデータを初期化のデフォルトデータに復元する.
コンポーネント構成のロード
現在のコンポーネントのidに基づいて現在のコンポーネントのpropsとstyle構成情報を見つけ、前のconfigで各フィールドのconfigに対応するコンポーネントを記載して編集する.
コンポーネントの削除
現在のコンポーネントidと親コンポーネントidに基づいて、このコンポーネントを削除し、現在選択されているコンポーネントに対する保存情報をすべてクリアし、localstorageを更新します.
コンポーネントのコピー
現在のコンポーネントidと親ノードidに基づいて、現在コピーされているコンポーネントのすべての情報を見つけ、新しいidを生成し、親コンポーネントにpushしてlocalstorageを更新します.
属性propsの編集
formフォームを生成し、各formitemのnameが現在のコンポーネントのkey-currentIdに設定され、formのitemのvalueが変更されるとconfigform全体の値を取得し、
スタイルの編集
一般的なcss構成プロパティを提供し、対応するkey値をチェックして以下にそのプロパティに対応する構成を生成し、フォームを構成し、itemの値が変更されたときに、すべてのチェック属性の値を収集し、現在のコンポーネントの構成に更新し、エディタを再レンダリングし、
tips:スタイル編集時に
テンプレート文字列 をプリセット現在のページの構成データ を再帰 は、コンポーネントタイプ に格納する.は、 は、 現在のコンポーネントchildrens処理、
は、コンポーネント から削除する.は、生成された を置き換える.
プレビューコードは を取得する.は、 を美化する. を美化する.
に表示する.は、ワンタッチレプリケーション を提供する.
ソースのダウンロードは を取得する.呼び出し である.呼び出し を削除する.プリセットは、 を生成する.は、 に書き込む. を追加する.生成 を返す.
インタフェースから戻る をダウンロードする.
検証#ケンショウ#
生成されたコードを
githubプロジェクトアドレス:taro-designer
オンライン体験アドレス:taro-desiger
主な技術点は以下の通りである.
背景
会社の一部の業務はインタラクティブな開発をしています.例えば、サイン、贈り物の両替などです.インタラクティブなビジネスは迅速な反復が必要であり、H 5、微信ウィジェット、淘宝ウィジェットをサポートする必要があるため、フロントエンドはtaroをベースフレームワークとして多端なニーズを満たすために採用している.そこで,基礎となるコンポーネントを可視化してドラッグ&ドロップし,ページレイアウトを直接生成し,開発効率を向上させるかどうかを考える.
プロジェクトの様々な限界に直面して、taro 2を採用した.xライブラリ、およびtaroが持参したコンポーネントライブラリ、taro-uiではありません.taroがサポートするプロパティはバラツキがあるため、ビジネス側と議論した後、tarojsコンポーネントライブラリがサポートするh 5と微信ウィジェットの交差を取ってプロパティ編集を行います.
テクノロジースタック
react、mobx、cloud-react、tarojs
ドラッグ&ドロップ
左側の選択可能なコンポーネントからエディタに要素をドラッグ&ドロップし、エディタで2回のドラッグ&ドロップソートを行い、ドラッグ位置エラーを解決し、再ドラッグの問題を削除する必要があります.
ドラッグ&ドロップのベースライブラリとしてreact-dndを採用し,具体的な使い方はプロジェクトの実践と文章の説明が単独であり,ここでは後述しない.
プロジェクトコード:react-dnd-nested
demoアドレス:react-dnd-nested-demo
パッケージアセンブリ
ここで包装されているのはtaroのコンポーネントであり、他のサードパーティのコンポーネントであってもよい.各コンポーネントは、
index.js
コンポーネントをパッケージするためのコードと、config.json
コンポーネントの構成データのためのSwitch
ファイルとを含む.// Switch index.js
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { Switch } from '@tarojs/components/dist-h5/react';
export default class Switch1 extends Component {
render() {
const { style, ...others } = this.props;
return ;
}
}
Switch1.propTypes = {
checked: PropTypes.bool,
type: PropTypes.oneOf(['switch', 'checkbox']),
color: PropTypes.string,
style: PropTypes.string
};
Switch1.defaultProps = {
checked: false,
type: 'switch',
color: '#04BE02',
style: ''
};
// config.json
{
//
"type": "Switch",
//
"name": " ",
//
"canPlace": false,
// props , index.js defaultProps
"defaultProps": {
"checked": false,
"type": "switch",
"color": "#04BE02"
},
//
"defaultStyles": {},
// props
"config": [
{
// key
"key": "checked",
// : Input、Radio、Checkbox、Select
"type": "Radio",
//
"label": " "
},
{
"key": "type",
"type": "Select",
"label": " ",
//
"dataSource": [
{
"label": "switch",
"value": "switch"
},
{
"label": "checkbox",
"value": "checkbox"
}
]
},
{
"key": "color",
"label": " ",
"type": "Input"
}
]
}
プリセットスクリプト
コードは人間よりも効率的で、正確で、信頼できると信じています.
コンポーネントテンプレートスクリプトの生成
各コンポーネントはtaro対応のコンポーネントをパッケージしているので、
index.js
とconfig.json
ファイルのコードを予め設定し、コードには__ComponentName__
の特殊文字をコンポーネント名として設定し、生成スクリプトを実行し、ユーザーの入力から読み込んで正則に置き換えることで、ベースのコードを生成することができます.これは、特定のコードを表示します.生成スクリプトは次のとおりです.const path = require('path');
const fs = require('fs');
const readline = require('readline').createInterface({
input: process.stdin,
output: process.stdout
});
readline.question(' ?', name => {
const componentName = name;
readline.close();
const targetPath = path.join(__dirname, '../src/components/');
fs.mkdirSync(`${targetPath}${componentName}`);
const componentPath = path.join(__dirname, `../src/components/${componentName}`);
const regx = /__ComponentName__/gi
const jsContent = fs.readFileSync(path.join(__dirname, '../scripts/tpl/index.js')).toString().replace(regx, componentName);
const configContent = fs.readFileSync(path.join(__dirname, '../scripts/tpl/config.json')).toString().replace(regx, componentName);
const options = { encoding: 'utf8' };
fs.writeFileSync(`${componentPath}/index.js`, jsContent, options, error => {
if (error) {
console.log(error);
}
});
fs.writeFileSync(`${componentPath}/config.json`, configContent, options, error => {
if (error) {
console.log(error);
}
});
});
package.json
は次のように構成されています."new": "node scripts/new.js",
スクリプトの実行
npm run new
出力
export
スクリプトすべてのコンポーネントの対外出力を
components/index.js
ファイルに配置する必要があります.コンポーネントを追加するたびに、このファイルを変更し、新しいコンポーネントの対外出力とプロファイルを追加する必要があります.そこで、新しいコンポーネントを生成するたびに、スクリプトを直接実行し、自動的に読み取り、ファイルを書き換え、外部出力します./**
* componets index.js
*/
const path = require('path');
const fs = require('fs');
const prettier = require('prettier');
function getStringCodes() {
const componentsDir = path.join(__dirname, '../src/components');
const folders = fs.readdirSync(componentsDir);
// ignore file
const ignores = ['.DS_Store', 'index.js', 'Tips'];
let importString = '';
let requireString = '';
let defaultString = 'export default {
';
let configString = 'export const CONFIGS = {
';
folders.forEach(folder => {
if (!ignores.includes(folder)) {
importString += `import ${folder} from './${folder}';
`;
requireString += `const ${folder.toLowerCase()}Config = require('./${folder}/config.json');
`;
defaultString += `${folder},
`;
configString += `${folder}: ${folder.toLowerCase()}Config,
`;
}
});
return { importString, requireString, defaultString, configString };
}
function generateFile() {
const { importString, requireString, defaultString, configString } = getStringCodes();
const code = `${importString}
${requireString}
${defaultString}
};
${configString}
};
`;
const configPath = path.join(__dirname, '../.prettierrc');
prettier.resolveConfig(configPath).then(options => {
const content = prettier.format(code, Object.assign(options, { parser: 'babel' }));
const targetFilePath = path.join(__dirname, '../src/components/index.js');
fs.writeFileSync(targetFilePath, content, error => {
if (error) {
console.log(error);
}
});
});
}
generateFile();
package.json
は次のように構成されています."gen": "node scripts/generate.js"
スクリプトの実行
npm run gen
データ構造
ページのインタラクティブデータは、
localstorage
のcacheData
配列に格納され、各コンポーネントのデータモデル:{
id: 1,
//
type: "View",
// props
props: {},
// style
styles: {},
//
chiildrens: []
}
簡単なページデータの例は次のとおりです.
[
{
"id": 1,
"type": "View",
"props": {},
"styles": {
"minHeight": "100px"
},
"childrens": [
{
"id": 9397,
"type": "Button",
"props": {
"content": "ok",
"size": "default",
"type": "primary",
"plain": false,
"disabled": false,
"loading": false,
"hoverClass": "none",
"hoverStartTime": 20,
"hoverStayTime": 70
},
"styles": {}
},
{
"id": 4153,
"type": "View",
"props": {
"hoverClass": "none",
"hoverStartTime": 50,
"hoverStayTime": 400
},
"styles": {
"minHeight": "50px"
},
"childrens": [
{
"id": 7797,
"type": "Icon",
"props": {
"type": "success",
"size": 23,
"color": ""
},
"styles": {}
},
{
"id": 9713,
"type": "Slider",
"props": {
"min": 0,
"max": 100,
"step": 1,
"disabled": false,
"value": 0,
"activeColor": "#1aad19",
"backgroundColor": "#e9e9e9",
"blockSize": 28,
"blockColor": "#fff",
"showValue": false
},
"styles": {}
},
{
"id": 1739,
"type": "Progress",
"props": {
"percent": 20,
"showInfo": false,
"borderRadius": 0,
"fontSize": 16,
"strokeWidth": 6,
"color": "#09BB07",
"activeColor": "#09BB07",
"backgroundColor": "#EBEBEB",
"active": false,
"activeMode": "backwards",
"duration": 30
},
"styles": {}
}
]
},
{
"id": 8600,
"type": "Text",
"props": {
"content": "text",
"selectable": false
},
"styles": {}
},
{
"id": 7380,
"type": "Radio",
"props": {
"content": "a",
"checked": false,
"disabled": false
},
"styles": {}
}
]
}
]
エディタ
実現構想:
1、初期化で取得した値が空の場合、デフォルトデータは次のとおりです.
[
{
id: 1,
type: 'View',
props: {},
styles: {
minHeight: '100px'
},
childrens: []
}
]
2.
cacheData
配列を巡回し、Tree
とItem
の2つのコンポーネントのネストを使用してデータ構造を生成し、Item
コンポーネントのうちtype
値に基づいて現在のコンポーネント、render
から現在のページを取得する.コアコードは次のとおりです.// index.js
// tree.js
render() {
const { parentId, items, move } = this.props;
return (
<>
{items && items.length
? items.map(item => {
return - ;
})
: null}
>
);
}
const CurrentComponet = Components[type];
return (
this.handleClick({ id, parentId, type }, event)}>
);
3、左側からコンポーネントをドラッグしてエディタに入り、ドラッグした親コンポーネントidを見つけ、
push
を使用して現在のコンポーネントchildrens
を変更してデータを追加します.add(targetId, type) {
// push
const item = findItem(this.pageData, targetId);
const obj = {
// id
id: generateId(),
type,
// props
props: CONFIGS[type].defaultProps || {},
//
styles: CONFIGS[type].defaultStyles || {}
};
// childrens , push
if (item.childrens) {
item.childrens.push(obj);
} else {
//
item.childrens = [obj];
}
localStorage.setItem(KEY, JSON.stringify(this.pageData));
}
4、エディタにコンポーネントをドラッグし、moveでコンポーネントを新しい親コンポーネントの下に移動する
index
を見つけ、指定の位置に5、コンポーネントをクリックすると、右側のエディタ領域に
props
とstyle
の構成情報が表示されます.6、ワークスペースを空にし、二次確認を追加して誤操作を防止し、ページデータを初期化のデフォルトデータに復元する.
単一コンポーネントアクション
コンポーネント構成のロード
現在のコンポーネントのidに基づいて現在のコンポーネントのpropsとstyle構成情報を見つけ、前のconfigで各フィールドのconfigに対応するコンポーネントを記載して編集する.
コンポーネントの削除
現在のコンポーネントidと親コンポーネントidに基づいて、このコンポーネントを削除し、現在選択されているコンポーネントに対する保存情報をすべてクリアし、localstorageを更新します.
コンポーネントのコピー
現在のコンポーネントidと親ノードidに基づいて、現在コピーされているコンポーネントのすべての情報を見つけ、新しいidを生成し、親コンポーネントにpushしてlocalstorageを更新します.
属性propsの編集
formフォームを生成し、各formitemのnameが現在のコンポーネントのkey-currentIdに設定され、formのitemのvalueが変更されるとconfigform全体の値を取得し、
cacheData
で現在のコンポーネントを検索し、propsを更新し、コンパイラを再レンダリングし、localstorage
を更新します.スタイルの編集
一般的なcss構成プロパティを提供し、対応するkey値をチェックして以下にそのプロパティに対応する構成を生成し、フォームを構成し、itemの値が変更されたときに、すべてのチェック属性の値を収集し、現在のコンポーネントの構成に更新し、エディタを再レンダリングし、
localstorage
を更新します.tips:スタイル編集時に
className
の生成が独立したcss
ファイルにあり、追加しないと行内スタイルが生成されます.taroのソースコードを生成
localstorage
から取得する.renderElementToJSX
データをjsx
文字列に変換type
を配列className
が存在するか否かを判断する.classNameをアルパカに変換して名前を付け、css modulesの使用を容易にし、renderCss
メソッドを呼び出してcss文字列を接続します.存在しない場合、renderInlineCss
を呼び出して行内スタイルを生成し、jsxに接続します.renderProps
を呼び出して各コンポーネントのprops構成を生成し、現在のprops値がデフォルトの値と等しいかどうかをフィルタリングし、その属性の判断を等しく取り除き、jsx文字列を簡略化する.childrens
またはcontent
フィールドが存在する場合、現在のコンポーネントのchildrenを処理する.そうでなければ、現在のコンポーネントは自閉和のコンポーネントです.type
に保存するデータを、jsx
文字列およびtypes
を使用して、プリセットテンプレートのプレースホルダ詳細コードの表示 ソースのプレビューとダウンロード
プレビューコード
renderJSONtoJSX
メソッドを呼び出し、生成されたjsx
およびcss
文字列format
apiを呼び出し、jsx
およびcss
文字列をフォーマットするprettier
とbabel
を使用してjsx
prettier
とless
を使用してcss
api
から戻る結果をコードプレビュー領域jsx
およびcss
の機能ソースのダウンロード
renderJSONtoJSX
メソッドを呼び出し、生成されたjsx
およびcss
文字列download
apiresponse header
が設けるContent-Type
はapplication/zip
fs.truncateSync
前回生成したファイルcode
という名前のフォルダjsx
およびcss
文字列を美化し、対応するファイルcode
フォルダにtaro.jsx
とindex.css
フォルダbase64
型のzip
ファイルはdata
のデータを取得し、base64
でロードし、blob
ファイルを作成し、検証#ケンショウ#
生成されたコードを
taro-cli
を使用したプロジェクトプロジェクトにコピーして効果を検証します.