Flutter: React でいう制御されたコンポーネントの実装
September 13, 2022
インプットフォームを作成する際、React では「制御されたコンポーネント(Controlled Components)」と「非制御コンポーネント(Uncontrolled Components)」という2つの考え方があります。
- 制御されたコンポーネント – React
- 非制御コンポーネント – React
特性を一言で表すならば、制御されたコンポーネントは扱いが容易でバグが入り込みづらく、非制御コンポーネントは扱いが複雑になりやすいが処理性能は向上します。
Flutter でインプットフォームを作る場合、通常はそのインプットフォーム自身が状態を保持する(いまどのような文字列が入力されているか?など)ため、これは「非制御コンポーネント」です。
Flutter で制御されたコンポーネントの実装 #
Flutter での制御されたコンポーネントは以下のような実装で実現できます。
import 'package:flutter/material.dart';
class TextFieldWidget extends StatefulWidget {
const TextFieldWidget({
required this.text,
required this.onChanged,
super.key,
});
final String text;
final void Function(String) onChanged;
@override
State<TextFieldWidget> createState() => _TextFieldWidgetState();
}
class _TextFieldWidgetState extends State<TextFieldWidget> {
final TextEditingController _controller = TextEditingController();
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
final TextSelection previousSelection = _controller.selection;
_controller.text = widget.text;
_controller.selection = previousSelection;
return TextFormField(
controller: _controller,
onChanged: widget.onChanged,
);
}
}
説明 #
Widget build()
の部分について説明します。
@override
Widget build(BuildContext context) {
final TextSelection previousSelection = _controller.selection;
_controller.text = widget.text;
_controller.selection = previousSelection;
return TextFormField(
controller: _controller,
onChanged: widget.onChanged,
);
}
次の部分で、プロップスで受け取った text
を TextEditingController
の text
フィールドに設定しています。
_controller.text = widget.text;
これによりこのウィジェット(コンポーネント)の使用者側で保持している text
の値をインプットフォームに反映することができます。
そして、その前後にある TextSelection
の記述はカーソル位置を調整するためのものです。
final TextSelection previousSelection = _controller.selection;
_controller.selection = previousSelection;
このウィジェットは text
の値が変わるたびにリビルドされます。このとき入力カーソルの位置が先頭に戻ってしまいます。
つまりそのままだと1文字入力するたびにカーソルが先頭に戻ることになりますが、これを防ぐための記述です。
補足 #
リセットボタンなどで text
の中身を一気にクリアしたとき _controller.selection = previousSelection
のところでエラーになります。
これを防ぎつつ正常に挙動させるためには次のようにします。
@override
Widget build(BuildContext context) {
final TextSelection previousSelection = _controller.selection;
_controller.text = widget.text;
try {
_controller.selection = previousSelection;
} on FlutterError {
_controller.selection = TextSelection.fromPosition(
TextPosition(offset: _controller.text.length),
);
}
return TextFormField(
controller: _controller,
onChanged: widget.onChanged,
);
}
ご覧の通りなかなか無理矢理なコードです。Flutter では基本通りに非制御コンポーネントを使った方が良いかもしれませんね。