実践的チュートリアルとは別のやつです。
要素とコンポーネント
要素
要素は読み取り専用。
const elem1 = <h1>Hello, {name}</h1>;
const elem2 = <img src={user.avatarUrl}></img>;
コンポーネント
propsは読み取り専用。
更新したいならstateを使う。
// 関数コンポーネント
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}
// クラスコンポーネント
class Welcome extends React.Component {
render() {
return <h1>Hello, {this.props.name}</h1>;
}
}
コンポーネントの再利用
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}
function App() {
return (
<div>
<Welcome name="Sara" />
<Welcome name="Cahal" />
<Welcome name="Edite" />
</div>
);
}
state
クラスコンポーネントで「状態」を持つ。
stateへの直接代入するのはコンストラクタのみ。
stateを更新する時はsetStateを使う。
propsはsuperで親コンストラクタに渡す必要がある。
class Clock extends React.Component {
constructor(props) {
super(props);
this.state = {date: new Date()};
}
// stateの更新
update(){
this.setState({
date: new Date()
});
}
render() {
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {this.state.date.toLocaleTimeString()}.</h2>
</div>
);
}
}
state の更新は非同期に行われる可能性がある
よくわかってない
https://ja.reactjs.org/docs/state-and-lifecycle.html#state-updates-may-be-asynchronous
React はパフォーマンスのために、複数の setState() 呼び出しを 1 度の更新にまとめて処理することがあります。
this.props と this.state は非同期に更新されるため、次の state を求める際に、それらの値に依存するべきではありません。
例えば、以下のコードはカウンターの更新に失敗することがあります:
// Wrong
this.setState({
counter: this.state.counter + this.props.increment,
});
これを修正するために、オブジェクトではなく関数を受け取る setState() の 2 つ目の形を使用します。その関数は前の state を最初の引数として受け取り、更新が適用される時点での props を第 2 引数として受け取ります:
// Correct
this.setState((state, props) => ({
counter: state.counter + props.increment
}));
stateの値はそれぞれ個別に更新できる
class Post extends React.Component {
constructor(props) {
super(props);
this.state = {
date: [],
comment: []
};
}
update() {
this.setState({
date: new Date()
});
this.setState({
comment: "new comment."
});
}
}
ライフサイクル
クラスコンポーネントで利用可能。
class Clock extends React.Component {
constructor(props) {
super(props);
this.state = {date: new Date()};
}
componentDidMount() {
// ...
}
componentWillUnmount() {
// ...
}
// ...
}
TypeScriptでクラスコンポーネントを作成する場合
クラス宣言の型引数にpropsの型とstateの型を指定する。
type User = {
}
class Comment extends React.Component</* propsの型 */, /* stateの型 */> {
// ...
}
イベントハンドラ
onClickに関数を渡す。
イベントのデフォルト動作をキャンセルしたい場合、preventDefaultを呼び出す。
return falseではキャンセルされないので注意。
function ActionLink() {
function handleClick(e) {
e.preventDefault();
console.log('Clicked.');
}
return (
<a href="#" onClick={handleClick}>Click me</a>
);
}
コールバック内でthisを使う場合
コールバック内(下記コードのhandleClick)でthisを使う場合はバインドしておく必要がある。
class Toggle extends React.Component {
constructor(props) {
super(props);
this.state = {isToggleOn: true};
// This binding is necessary to make `this` work in the callback
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.setState(state => ({
isToggleOn: !state.isToggleOn
}));
}
render() {
return (
<button onClick={this.handleClick}>
{this.state.isToggleOn ? 'ON' : 'OFF'}
</button>
);
}
}
または関数ではなく関数型を変数で宣言する。
執筆時の2020.10.18では試験的な構文のため仕様変更の可能性あり。
class LoggingButton extends React.Component {
// This syntax ensures `this` is bound within handleClick.
// Warning: this is *experimental* syntax.
handleClick = () => {
console.log('this is:', this);
}
render() {
return (
<button onClick={this.handleClick}>
Click me
</button>
);
}
}
TypeScriptでイベントハンドラを作成する場合
Reactのイベントを受け取る型があるので引数にはそれを指定する。
function handleClick(e: React.MouseEvent){
e.preventDefault();
console.log("Clicked");
}
条件付きレンダー
render()でreturnする前に描画の中身を決めておく。
class LoginControl extends React.Component {
constructor(props) {
super(props);
this.handleLoginClick = this.handleLoginClick.bind(this);
this.handleLogoutClick = this.handleLogoutClick.bind(this);
this.state = {isLoggedIn: false};
}
handleLoginClick() {
this.setState({isLoggedIn: true});
}
handleLogoutClick() {
this.setState({isLoggedIn: false});
}
render() {
const isLoggedIn = this.state.isLoggedIn;
let button;
if (isLoggedIn) {
button = <LogoutButton onClick={this.handleLogoutClick} />;
} else {
button = <LoginButton onClick={this.handleLoginClick} />;
}
return (
<div>
<Greeting isLoggedIn={isLoggedIn} />
{button}
</div>
);
}
}
三項演算子でも条件分けできる。
render() {
const isLoggedIn = this.state.isLoggedIn;
return (
<div>
The user is <b>{isLoggedIn ? 'currently' : 'not'}</b> logged in.
</div>
);
}
render()でnullを返すと描画されない。
function WarningBanner(props) {
if (!props.warn) {
return null;
}
return (
<div className="warning">
Warning!
</div>
);
}
リスト表示
配列を描画することで複数コンポーネントをリストで表示できる。
keyはどのようさが変更、追加、削除されたのかをReactが識別するためのもので、要素同士の間で一意になる値を設定する。
配列が複数あっても、自身の配列内で一意であればよい。
function NumberList() {
const numbers = [1, 2, 3, 4, 5];
const listItems = numbers.map((number) =>
<li key={number.toString()}>
{number}
</li>
);
return (
<ul>{listItems}</ul>
);
}
一意になる安定した値がない場合はindexで代用できる。
ただし、並び順が変更される可能性がある場合、indexを用いることは推奨されない。
const todoItems = todos.map((todo, index) =>
// Only do this if items have no stable IDs
<li key={index}>
{todo.text}
</li>
);
keyはコンポーネントの引数として渡されない。
コンポーネント内でその値を使いたいときは別の名前のpropsとして渡す必要がある。
下記のコードで、Postコンポーネントはprops.idを読み取ることはできるがprops.keyは読み取ることができない。
const content = posts.map((post) =>
<Post
key={post.id}
id={post.id}
title={post.title} />
);
リスト化はJSX内で式として埋め込むこともできる。
function NumberList(props) {
const numbers = props.numbers;
return (
<ul>
{numbers.map((number) =>
<ListItem
key={number.toString()}
value={number} />
)}
</ul>
);
}
フォームのバインディングとイベントハンドラ
stateの値と紐付ける。
イベントはhtmlのイベントに関数(かjsの式)を紐付ければいい。
class NameForm extends React.Component {
// ...
handleSubmit(event){
event.preventDefault();
alert('A name was submitted: ' + this.state.value);
}
resetInput(event){
event.preventDefault();
this.setState({value: ''});
}
render(){
return (
<form onSubmit={this.handleSubmit}>
<label>
Name:
<input type="text" value={this.state.value} onChange={this.handleChange} />
</label>
<input type="submit" value="Submit" />
</form>
);
}
}
textarea
テキストエリアだけ書き方が異なる。
閉じタグなしでvalueをプロパティとして持たせる。
<textarea value={this.state.value} onChange={this.handleChange} />
select
selectedの代わりにvalueを指定する。
<select value={this.state.value} onChange={this.handleChange}>
<option value="grapefruit">Grapefruit</option>
<option value="lime">Lime</option>
<option value="coconut">Coconut</option>
<option value="mango">Mango</option>
</select>
複数選択する場合。
<select multiple={true} value={['B', 'C']}>
nameで更新するプロパティを指定
nameプロパティをstateで用いて複数のイベントを処理できる。
class Reservation extends React.Component {
constructor(props) {
super(props);
this.state = {
isGoing: true,
numberOfGuests: 2
};
this.handleInputChange = this.handleInputChange.bind(this);
}
handleInputChange(event) {
const target = event.target;
const value = target.type === 'checkbox' ? target.checked : target.value;
const name = target.name;
this.setState({
[name]: value
});
}
render() {
return (
<form>
<label>
Is going:
<input
name="isGoing"
type="checkbox"
checked={this.state.isGoing}
onChange={this.handleInputChange} />
</label>
<br />
<label>
Number of guests:
<input
name="numberOfGuests"
type="number"
value={this.state.numberOfGuests}
onChange={this.handleInputChange} />
</label>
</form>
);
}
}
ライブラリ
Formikが公式おすすめのライブラリっぽい。
stateのリフトアップ
ふたつのコンポーネントで入力した値を同期してほしいなど、stateを共有したいとき、共有の親コンポーネントにstateを移動することによって実現する。
これをstateのリフトアップと呼ぶ。
下記コードはガイドのコードがすこぶるわかりにくかったのでtsにして修正したもの
ふたつのInputを表示し、片方が変更されたらもう片方も同時に変更するようにする。
TemperatureInputではstateを持たず、その親であるCalculatorでcelsiusとfahrenheitの値を管理している。
type CalculatorStateType = {
celsius: string,
fahrenheit: string
}
class Calculator extends React.Component<{}, CalculatorStateType> {
constructor(props: {}){
super(props);
this.celsiusChanged = this.celsiusChanged.bind(this);
this.fahrenheitChanged = this.fahrenheitChanged.bind(this);
this.state = {celsius: '', fahrenheit: ''}
}
celsiusChanged(temperature: string) {
this.setState({
celsius: temperature,
fahrenheit: tryConvert(temperature, toFahrenheit)
});
}
fahrenheitChanged(temperature: string) {
this.setState({
fahrenheit: temperature,
celsius: tryConvert(temperature, toCelsius)
});
}
render() {
return (
<div>
<p>My Calculator</p>
<TemperatureInput scale="c" temperature={this.state.celsius}
onTemperatureChange={this.celsiusChanged} />
<TemperatureInput scale="f" temperature={this.state.fahrenheit}
onTemperatureChange={this.fahrenheitChanged} />
<BoilingVerdict celsius={parseFloat(this.state.celsius)} />
</div>
);
}
}
type TemperatureInputProps = {
scale: keyof nameType,
temperature: string,
onTemperatureChange: ((arg0: string)=>void)
}
class TemperatureInput extends React.Component<TemperatureInputProps, {}> {
constructor(props: TemperatureInputProps) {
super(props);
this.handleChange = this.handleChange.bind(this);
}
handleChange(event: React.ChangeEvent<HTMLInputElement>) {
// this.setState({temperature: event.target.value});
this.props.onTemperatureChange(event.target.value);
}
render() {
const temperature = this.props.temperature;
const scale = this.props.scale;
return (
<fieldset>
<legend>Enter temperature in {scaleNames[scale]}:</legend>
<input value={temperature} onChange={this.handleChange} />
</fieldset>
)
}
}
子要素を引数として渡す
親コンポーネントから子コンポーネントに要素を渡して描画できる。
ダイアログなど。
function WelcomeDialog() {
return (
<FancyBorder color="blue">
<h1 className="Dialog-title">
Welcome
</h1>
<p className="Dialog-message">
Thank you for visiting our spacecraft!
</p>
</FancyBorder>
);
}
function FancyBorder(props) {
return (
<div className={'FancyBorder FancyBorder-' + props.color}>
{props.children}
</div>
);
}
複数要素を引数として渡す
デフォルトの引数名はchildrenだが名前を付けて複数受け取ることもできる。
function SplitPane(props) {
return (
<div className="SplitPane">
<div className="SplitPane-left">
{props.left}
</div>
<div className="SplitPane-right">
{props.right}
</div>
</div>
);
}
function App() {
return (
<SplitPane
left={
<Contacts />
}
right={
<Chat />
} />
);
}
ガイドのサンプルプロダクト