コアコンセプト / プロパティシステム

プロパティシステム

NativeScriptは、有名なJavaScriptのObject.definePropertyのラッパーに基づく独自のプロパティシステムを提供します。 UIおよびCSS要素を使用したモバイル開発のコンテキストで優れた開発利便性を提供するために、Propertyクラスの拡張クラスを提供しました。 この記事では、提供されたプロパティクラスと、初期化、登録、ビューライフサイクル、変更されたイベントのリサイクルと処理など、ビューとプロパティを操作する際の基本テクニックについて説明します。 Viewの一般的に使用されるいくつかの方法も示されています。

プロパティシステムクラス

すべてのプロパティクラスの実装は、tns-core-modules/ui/core/propertiesモジュールの下にあります。 以下では、そのモジュールから公開されているすべてのクラスを見ていきます。

プロパティクラス

プロパティは、valueChangevalueConverterequityComparerなどの追加のコールバックを備えた "Object.defineProperty"の単純なラッパーです。 プロパティを定義するとき、所有タイプとプロパティのタイプを指定します。

export const textProperty = new Property<MyButtonBase, string>({
	name: "text",
	defaultValue: "",
	affectsLayout: true
});
textProperty.register(MyButtonBase);

textPropertyを見る:Property <MyButtonBase、string> - 所有タイプはMyButtonBaseです。 つまり、このプロパティはMyButtonBaseのインスタンスで定義されます。 プロパティのタイプはstringなので、任意のテキストを受け入れます。

valueChangeイベントは、プロパティの値が変更されたときに実行されます。 プロパティのタイプがstringでない場合、valueConverterequalityComparerを指定する必要があります。 このプロパティに(XMLやCSSなどから)文字列値が設定されている場合、valueConverterが呼び出され、可能であればその文字列を意味のある値に変換するか、できない場合は例外をスローする必要があります。 equalityComparerが指定されている場合、値がプロパティに設定されるたびに呼び出されます。 そこで、現在の値と新しい値が等しいかどうかを比較できます。 たとえば、プロパティのタイプがColorの場合、Color.equalsequalityComparer関数として使用できるため、 Colorの新しいインスタンスが設定されている場合、現在の色と新しい色のargbが同じ値でも、比較子はfalseを返します。

Propertyコンストラクターにはもう1つのオプションがあります:effectsLayout: booleanです。 このプロパティに新しい値としてtrueを設定すると、新しいレイアウトパスがトリガーされます。 これはパフォーマンスの最適化として行われます。 Androidにはレイアウトシステムが統合されているため、ほとんどの場合、必要に応じて無効になります。 したがって、 'isIOS' ブーリアンプロパティを使用するなど、iOSのみに対してaffectsLayouttrueとして定義することにより、1つのネイティブ呼び出しをスキップします。 このプロパティがレイアウトに影響する可能性があることがわかっている場合、iOSには統合レイアウトシステムがないため、Propertyコンストラクタで指定する必要があります。

このプロパティを設定して要素のサイズや位置を変更する場合、フラグaffectsLayouttrue(主にiOSの場合)でなければなりません。 たとえば、この例では、ボタンのテキストを別の値に設定すると、ボタンの幅が広くなったり短くなったりして要素の寸法に影響するため、affectsLayout: isIOSとして指定します。 このプロパティが要素の位置/サイズを変更しない場合、affectsLayoutを指定する必要はありません。 たとえば、background-colorプロパティは要素の位置/サイズを変更しません。

注釈: プラットフォーム固有の実装では、プロパティオブジェクト(例:textProperty)のgetDefaultシンボルとsetNativeシンボルを使用して、 このプロパティをネイティブビューに適用する方法を定義します。 getDefaultメソッドは、setNativeの最初の呼び出しの前に1回だけ呼び出されるため、このプロパティのデフォルトのネイティブ値がわかります。 返される値は、ネイティブビューをリサイクルすることを決定したときにsetNativeメソッドに渡されます。 コントロールのネイティブビューのリサイクルは、recycleNativeViewフィールドがtrueに設定されている場合にのみ行われます。

CssPropertyクラス

CssPropertyは、Propertyタイプに非常に良く似ていますが、2つの小さな違いがあります。

export const myOpacityProperty = new CssProperty<Style, number>({
	name: "myOpacity",
	cssName: "my-opacity",
	defaultValue: 1,
	valueConverter: (v) => {
		const x = parseFloat(v);
		if (x < 0 || x > 1) {
			throw new Error(`opacity accepts values in the range [0, 1]. Value: ${v}`);
		}

		return x;
	}
});
myOpacityProperty.register(Style);
注釈: キーフレームアニメーションを使用してアニメーション化できるCSSプロパティの場合、オプションのkeyframeパラメーターに付随する拡張CssAnimationPropertyを使用できます。

InheritedCssPropertyクラス

InheritedCssPropertyは、スタイルタイプで定義されたプロパティです。 これらは、CSSで設定でき、その子に値を伝播する継承可能なCSSプロパティです。 これらは、FontSize、FontWeight、Colorなどのプロパティです。

export const selectedBackgroundColorProperty = new InheritedCssProperty<Style, Color>({
	name: "selectedBackgroundColor",
	cssName: "selected-background-color",
	equalityComparer: Color.equals,
	valueConverter: (v) => new Color(v)
});
selectedBackgroundColorProperty.register(Style);

ShorthandPropertyクラス

ShorthandPropertyは、CSSプロパティのショートハンド構文規則を提供します。たとえば、以下のような4つのすべてのマージンに対する明示的な構文の代わりに、

margin-top:  0;
margin-right: 10;
margin-bottom: 0;
margin-left: 10;
ユーザーは次のような簡略margin構文を使用したいでしょう。
margin: 0 10 0 10;

簡略marginプロパティを作成するには、すべてのCSSプロパティを定義する必要があります。 このようにこれらを使用して、短縮形のプロパティゲッターで構文ルールを設定できます。

const marginProperty = new ShorthandProperty<Style, string | PercentLength>({
	name: "margin",
	cssName: "margin",
	getter: function (this: Style) {
		if (PercentLength.equals(this.marginTop, this.marginRight) &&
			PercentLength.equals(this.marginTop, this.marginBottom) &&
			PercentLength.equals(this.marginTop, this.marginLeft)) {
			return this.marginTop;
		}
		return `${PercentLength.convertToString(this.marginTop)} ${PercentLength.convertToString(this.marginRight)} ${PercentLength.convertToString(this.marginBottom)} ${PercentLength.convertToString(this.marginLeft)}`;
	},
	converter: convertToMargins
});
marginProperty.register(Style);

export const marginLeftProperty = new CssProperty<Style, PercentLength>({ name: "marginLeft", cssName: "margin-left", defaultValue: zeroLength, affectsLayout: isIOS, equalityComparer: Length.equals, valueConverter: PercentLength.parse });
marginLeftProperty.register(Style);

export const marginRightProperty = new CssProperty<Style, PercentLength>({ name: "marginRight", cssName: "margin-right", defaultValue: zeroLength, affectsLayout: isIOS, equalityComparer: Length.equals, valueConverter: PercentLength.parse });
marginRightProperty.register(Style);

export const marginTopProperty = new CssProperty<Style, PercentLength>({ name: "marginTop", cssName: "margin-top", defaultValue: zeroLength, affectsLayout: isIOS, equalityComparer: Length.equals, valueConverter: PercentLength.parse });
marginTopProperty.register(Style);

export const marginBottomProperty = new CssProperty<Style, PercentLength>({ name: "marginBottom", cssName: "margin-bottom", defaultValue: zeroLength, affectsLayout: isIOS, equalityComparer: Length.equals, valueConverter: PercentLength.parse });
marginBottomProperty.register(Style);

CoerciblePropertyクラス

CoerciblePropertyは、強制可能な機能を提供することにより、基本Propertyクラスを拡張するプロパティです。 プロパティを強制する必要がある場合の説明をわかりやすくするために、異なる数のアイテムを保持できるUI要素のselectedIndexプロパティで作業していると仮定しましょう。 基本ケースは、selectedIndexがアイテムの数内で異なることを示唆しますが、アイテムが動的に変更される場合(およびselectedIndexが長さの範囲内にない場合)は 何をカバーしますか? これは、値を強制できるプロパティで処理できる場合です。

selectedIndexアイテムの数に依存する強制可能なプロパティの作成

export const selectedIndexProperty = new CoercibleProperty<SegmentedBarBase, number>({
	name: "selectedIndex", defaultValue: -1,
	valueChanged: (target, oldValue, newValue) => {
		target.notify(<SelectedIndexChangedEventData>{ eventName: SegmentedBarBase.selectedIndexChangedEvent, object: target, oldIndex: oldValue, newIndex: newValue });
	},

	// in this case the coerse value will change depending on whether the actual number of items
	// is more or less than the value we want to appl for selectedIndex
	coerceValue: (target, value) => {
		let items = target.items;
		if (items) {
			let max = items.length - 1;
			if (value < 0) {
				value = 0;
			}
			if (value > max) {
				value = max;
			}
		} else {
		  value = -1;
		}

		return value;
	},

	valueConverter: (v) => parseInt(v)
});
selectedIndexProperty.register(SegmentedBarBase);

itemsプロパティを設定するとき、selectedIndexを強制します

[itemsProperty.setNative](value: SegmentedBarItem[]) {
	this.nativeViewProtected.clearAllTabs();

	const newItems = value;
	if (newItems) {
		newItems.forEach((item, i, arr) => this.insertTab(item, i));
	}

	selectedIndexProperty.coerce(this);
}

プロパティの登録

プロパティを定義したら、次のようなタイプに登録する必要があります。

textProperty.register(MyButtonBase);

CssPropertiesは次のようにStyleクラスに登録する必要があります。

// Augmenting Style definition so it includes our myOpacity property
	declare module "tns-core-modules/ui/styling/style" {
	interface Style {
		myOpacity: number;
	}
}

// Defines 'myOpacity' property on Style class.
myOpacityProperty.register(Style);

この登録により、registerメソッドに渡される型のプロパティを定義します。

注釈: クラス定義の後に必ずregister呼び出しを配置してください。そうしないと、例外が発生します。

値変更イベント

プロパティ値の変更時に通知を取得するには、addNameListenerメソッドのeventNameとしてpropertyNameChangeを指定する必要があります。

textField.addEventListener('textChange', handler...)

NativeViewプロパティ

iosおよびandroidプロパティの代わりにnativeViewを使用します。 iosおよびandroidプロパティは互換性のために残されています。 ただし、すべてのビューライフサイクルメソッドとネイティブプロパティコールバック(以下で説明)は、nativeViewプロパティで動作する必要があります。

ビューのライフサイクルとリサイクル

NativeScript 3.0はnativeViewリサイクルを導入しました。 nativeViewのリサイクルは、Androidで非常にコスト高な操作であるネイティブビューのインスタンス化を減らすことを目的としています。 リサイクルできるようにするには、ビューから公開されるすべてのプロパティがプロパティシステムである必要があります。

プロパティ値の最初の変更時に取得されるプロパティのデフォルト値を取得するメソッドがあります。 ビューがもう必要ないことがわかったら、ネイティブビューを元の状態にリセットし、同じタイプの将来のビューで再利用できるマップに配置します。 これには3つの新しい重要な方法があります。

Androidでは、モジュールのルートにあるネイティブ型へのアクセスを避けます(ClickListenerは、実行時に呼び出される関数で宣言および実装されることに注意してください)。 これは、Androidランタイムが実行されていないホストマシンで生成されるV8スナップショット機能固有です。 重要なのは、モジュールのルート(たとえば、関数内ではない)でネイティブ型、メソッド、フィールド、名前空間などにアクセスする場合、コードはV8スナップショット機能と互換性がないことです。 最も簡単な回避策は、上記のinitializeClickListener関数のような関数でラップすることです。

この実装では、シングルトンリスナー(Androidの場合clickListener)およびハンドラー(iOSの場合handler)を使用して、 ネイティブクラスをインスタンス化する必要性を減らし、メモリ使用量を削減します。 可能であれば、そのような手法を使用してネイティブ呼び出しを減らすことをお勧めします。

ビューの子の繰り返し

ビュー階層をトラバースできるようにする2つのメソッドがあります。 どちらも、子に対して呼び出されるcallback関数を受け入れます。 コールバックはブール値を返す必要があり、偽である場合、反復が中断します。 これは、特定のビューを検索していて、見つかったらすぐに反復を停止する場合に特に便利です。

Viewの子を使用するには:

public eachChildView(callback: (child: View) => boolean): void

このメソッドは、以前は_eachChildView()と呼ばれていました。 Viewの子孫のみを返します。 たとえば、TabViewItemはViewBase型であるため、TabViewは各TabViewItemのビューを返します。

ViewBaseの子を使用するには:

public eachChild(callback: (child: ViewBase) => boolean): void;

このメソッドは、ViewBaseを含むすべてのビューを返します。 プロパティシステムによって使用され、ネイティブセッターの適用、継承されたプロパティの伝播、スタイルの適用などが行われます。 TabViewの場合、このメソッドは、TabViewItemsも返すので、CSSを使用してスタイル設定できます。

クラス共通メソッドの表示

各UI要素は、Viewクラス(StackLayoutLabelなど)を拡張し、 UI開発を容易にするために作成された一連のメソッドを備えています。 測定と配置に関連するメソッドは、すべてのレイアウト測定がパスしたことを確認するために、現在のPagenavigatedToイベントで呼び出す必要があります。

View Properties module Property CssProperty CssAnimationProperty InheritedCssProperty ShorthandProperty CoercibleProperty isIOS
入門

コアコンセプト

ユーザーインターフェース

ツール

ハードウェアアクセス

プラグインの開発

リリース

アプリテンプレート

パフォーマンスの最適化

フレームワークモジュール

ガイド

サポートを受ける

トラブルシューティング

Siedkick