反応における再帰的レンダリング:ユニバーサルJSONレンダリング器の構築


昨年、私は仕事のために、そして、私自身の満足のために広範囲に反応して働いていました.私はいくつかのかなり面白いパターンにつまずいたが、再帰コンポーネントをレンダリングする際に使用される再帰を見なかった.あなたがコンピュータサイエンス教育のどんな種類でもあるならば、あなたは多分何の再発がかなり早いかについて教えられました.基本的に、どのような再帰が実行されているのかは、引数が現在定義されているベースケースになるまで、現在の関数とまったく同じ関数を呼び出しています.
最近、私はAPIを通して得られるいくつかのJSONをレンダリングする反応コンポーネントを作成する仕事をしました.あなたがウェブ開発者であるならば、あなたはおそらくあなたがAPIが戻るものを100 %確信することができないと気がつきます、そして、あなたがそれがそうであると思わないならば、あなたがそれについて考えると、私はあなたに考えます.Webは静的に入力されません.この記事では、APIは常に何らかの種類のJSON(または何も)を返します.
私がレンダリングする必要があるJSONは、複数の階層レベルを持つ、醜い、ネストしたものだった.あなたはそれらのレベルが設定されるかどうかを知ることができなかったかどうかを、彼らは空の配列やヌルなど、ナイーブな方法は、すべての階層レベルのコンポーネントを作成することですし、“ああ、二度と”の考え方でされている数分後、私は再帰的にこれらのレベルを、エキサイティングなレンダリングにアイデアを持っていた!コンピュータプログラマーとして、私は絶対に再帰と楽しい方法これらの種類の問題を解決するが大好きです.

コンポーネントの作成


我々がテストするダミーJSONはJSON Generator . ご覧のように、空の値、空の配列、空のオブジェクト、null値の配列、null値を持つオブジェクトがあります.私たちの最大の深さは4レベルです.
const testJson = {
  "_id": "5bc32f3f5fbd8ad01f8265fd",
  "index": 0,
  "guid": "87cfbb5d-71fb-45a7-b268-1df181da901c",
  "isActive": true,
  "balance": "$3,583.12",
  "picture": "http://placehold.it/32x32",
  "age": 31,
  "eyeColor": "brown",
  "nullTestValue": null,
  "arrayWithNulls": [null, null, null],
  "objectWithNulls": {
     "firstNullValue": null,
     "secondNullValue": null     
  },
  "name": "Becky Vega",
  "gender": "female",
  "company": "ZOID",
  "email": "[email protected]",
  "phone": "+1 (957) 480-3973",
  "address": "426 Hamilton Avenue, Holtville, New Hampshire, 3431",
  "about": "Duis do occaecat commodo velit exercitation aliquip mollit ad reprehenderit non cupidatat dolore ea nulla. Adipisicing ea voluptate qui sunt non culpa labore reprehenderit qui non. Eiusmod ad do in quis cillum sint pariatur. Non laboris ullamco ea voluptate et anim qui quis id exercitation mollit ullamco dolor incididunt. Ad consequat anim velit culpa. Culpa Lorem eiusmod cupidatat dolore aute quis sint ipsum. Proident voluptate occaecat nostrud officia.\r\n",
  "registered": "2016-11-19T01:14:28 -01:00",
  "latitude": -80.66618,
  "longitude": 65.090852,
  "tags": [
    "ea",
    "officia",
    "fugiat",
    "anim",
    "consequat",
    "incididunt",
    "est"
  ],
  "friends": [
    {
      "id": 0,
      "name": "Genevieve Cooke",
      "ownFriends": {
         "1": "Rebbeca",
         "2": "Julia",
         "3": "Chopper only"
      },
    },
    {
      "id": 1,
      "name": "Eaton Buck"
    },
    {
      "id": 2,
      "name": "Darla Cash"
    }
  ],
  "greeting": "Hello, Becky Vega! You have 8 unread messages.",
  "favoriteFruit": "strawberry"
}
私たちは、TypesScriptで新しい反応プロジェクトを作成することから始めます.
yarn create react-app recursive-component --scripts-version=react-scripts-ts
次に、我々のJSONをレンダリングするための新しい反応コンポーネントを作成することができます.これを再帰的なプロパティと呼ぶことができます.理由は、それがベースケースに達すると、単一のJSONプロパティとその値をレンダリングします.
コンポーネントとファイルの構造はこのようになります.
import * as React from 'react';

interface IterableObject {
  [s: number]: number | string | boolean | IterableObject;
}

interface Props {
  property: number | string | boolean | IterableObject;
  propertyName: string;
  rootProperty?: boolean;
  excludeBottomBorder: boolean;
}

const RecursiveProperty: React.SFC<Props> = props => {

  return(
    <div>Our future component</div>
  );
}

export default RecursiveProperty;

我々は今、このコンポーネントをアプリケーションでレンダリングできます.TSX
import * as React from 'react';
import './App.css';

import logo from './logo.svg';
import RecursiveProperty from './RecursiveProperty';

class App extends React.Component {
  public render() {
    return (
      <div className="App">
        <header className="App-header">
          <img src={logo} className="App-logo" alt="logo" />
          <h1 className="App-title">Welcome to React</h1>
        </header>
        <div className="App-intro">
          <RecursiveProperty property={testJson} propertyName="Root Property" excludeBottomBorder={false} rootProperty={true}/>
        </div>
      </div>
    );
  }
}

export default App;

const testJson = ...
削除しましたtext-align: center からApp.css and added margin: 0 auto and width: 60% to .App-intro 我々のリストをうまくセンターにするクラス.
次に、我々の条件を書く必要があります.コンポーネントが葉(階層木の最後のノード)かどうかチェックする必要があります.それがそうであるならば、それはプロパティ名とその値を与えます.そうでない場合は、プロパティとして渡された次の階層レベルで再び再帰的にコンポーネントを呼び出します.
スタイルのコンポーネントを使用して少しスタイリングを追加できるように、各プロパティのコンテナを作成します.コンテナーは、各階層レベルが前より少しインデントになるように左マージンを設定します.
この場合、葉であるプロパティをレンダリングしようとします.JSONでは、JSONドキュメントのルートは、後に処理できるiterableオブジェクトであるため、「葉ではない」と表示されます.私たちは、葉が常に3つの基本的なJSONタイプの1つであるということを知っています.
import * as React from 'react';
import styled from 'styled-components';

interface IterableObject {
  [s: string]: number | string | boolean | IterableObject;
}

interface Props {
  property: number | string | boolean | IterableObject;
  propertyName: string;
  rootProperty?: boolean;
  excludeBottomBorder: boolean;
}

const RecursivePropertyContainer = styled.div`
  padding-top: 10px;
  padding-left: 3px;
  margin-left: 10px;
  ${(props: { excludeBottomBorder: boolean }) =>
    props.excludeBottomBorder ? '' : 'border-bottom: 1px solid #b2d6ff;'}
  color: #666;    
  font-size: 16px;
`;

export const PropertyName = styled.span`
  color: black;
  font-size: 14px;
  font-weight: bold;
`;

const RecursiveProperty: React.SFC<Props> = props => {
  return (
    <RecursivePropertyContainer excludeBottomBorder={props.excludeBottomBorder}>
      {props.property ? (
        typeof props.property === 'number' ||
        typeof props.property === 'string' ||
        typeof props.property === 'boolean' ? (
          <React.Fragment>
            <PropertyName>{camelCaseToNormal(props.propertyName)}: </PropertyName>
            {props.property.toString()}
          </React.Fragment>
        ) : (
          "It isn't a leaf"
        )
      ) : (
        'Property is empty'
      )}
    </RecursivePropertyContainer>
  );
};

const camelCaseToNormal = (str: string) => str.replace(/([A-Z])/g, ' $1').replace(/^./, str2 => str2.toUpperCase());

export default RecursiveProperty;
Camelcasetonormalメソッドはかなり自明です、それはスペースで通常のテキストにキャメルケーステキストを変換します.
次に、次のレベルで再度コンポーネントを呼び出す必要があります.JSON -オブジェクトの配列、またはキー/値のペアを持つiterableオブジェクトのデータリストを表す2つの方法があります.どちらの場合も、プロパティを新しいRecursivePropertyにマップする必要があります.
iterableオブジェクトがあれば、オブジェクトを使用します.value ()メソッドを値の配列を取得する( ES 7メソッドですので、tsconfig . jsonのlibプロパティに含めるようにしてください).プロパティ名を子に渡すには、オブジェクトを使用します.getownPropertyName ()メソッド.これは、プロパティ名の配列を返します、そして、我々は特定の名前への安全なアクセスをすることができます.map ()メソッド.このメソッドについて素晴らしいのは、配列で動作し、プロパティキーの代わりにインデックスを返すことです.
コンポーネントreturn ()はこのようになります.
return (
  <RecursivePropertyContainer excludeBottomBorder={props.excludeBottomBorder}>
    {props.property ? (
      typeof props.property === 'number' ||
      typeof props.property === 'string' ||
      typeof props.property === 'boolean' ? (
        <React.Fragment>
          <PropertyName>{camelCaseToNormal(props.propertyName)}: </PropertyName>
          {props.property.toString()}
        </React.Fragment>
      ) : (
        Object.values(props.property).map((property, index, { length }) => (
          <RecursiveProperty
            key={index}
            property={property}
            propertyName={Object.getOwnPropertyNames(props.property)[index]}
            excludeBottomBorder={index === length - 1}
          />
        ))
      )
    ) : (
      'Property is empty'
    )}
  </RecursivePropertyContainer>
);
これで、入れ子になったオブジェクトを折りたたみ、展開することができ、最初のレンダリングの葉の値だけを表示することができます.
expandablePropertyという新しいコンポーネントを作成できます.
import * as React from 'react';
import styled from 'styled-components';

export const PropertyName = styled.div`
  color: #008080;
  font-size: 14px;
  font-weight: bold;
  cursor: pointer;
`;

interface Props {
  title: string;
  expanded?: boolean;
}

interface State {
  isOpen: boolean;
}

export default class ExpandableProperty extends React.Component<Props, State> {
  state = {
    isOpen: !!this.props.expanded
  };

  render() {
    return (
      <React.Fragment>
        <PropertyName onClick={() => this.setState({ isOpen: !this.state.isOpen })}>
          {this.props.title}
          {this.state.isOpen ? '-' : '+'}
        </PropertyName>
        {this.state.isOpen ? this.props.children : null}
        {React.Children.count(this.props.children) === 0 && this.state.isOpen ? 'The list is empty!' : null}
      </React.Fragment>
    );
  }
}
我々は、現在我々を包むことができます.このコンポーネントではmap ()メソッドを使用します.
return (
  <RecursivePropertyContainer excludeBottomBorder={props.excludeBottomBorder}>
    {props.property ? (
      typeof props.property === 'number' ||
      typeof props.property === 'string' ||
      typeof props.property === 'boolean' ? (
        <React.Fragment>
          <PropertyName>{camelCaseToNormal(props.propertyName)}: </PropertyName>
          {props.property.toString()}
        </React.Fragment>
      ) : (
        <ExpandableProperty title={camelCaseToNormal(props.propertyName)} expanded={!!props.rootProperty}>
          {Object.values(props.property).map((property, index, { length }) => (
            <RecursiveProperty
              key={index}
              property={property}
              propertyName={Object.getOwnPropertyNames(props.property)[index]}
              excludeBottomBorder={index === length - 1}
            />
          ))}
        </ExpandableProperty>
      )
    ) : (
      'Property is empty'
    )}
  </RecursivePropertyContainer>
);
最後に、我々は行動でそれを見ることができます!

Vil il、我々はちょうど何か役に立つものを作りました!再帰はかなり反応して動作し、それは確かに私は将来的により多くの使用を行う素晴らしいツールです.私はあなたにもそれを使用するように奨励したい、それはかまない!
でソースコードを見つけることができますreact-recursive-component
乾杯!