質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.35%
Redux

Reduxは、JavaScriptアプリケーションの状態を管理するためのオープンソースライブラリです。ReactやAngularで一般的にユーザーインターフェイスの構築に利用されます。

React.js

Reactは、アプリケーションのインターフェースを構築するためのオープンソースJavaScriptライブラリです。

Q&A

解決済

1回答

2702閲覧

フォームにて、入力値が3文字以上ででsaveボタン押せるようにdisabledを入れたい

yuki-o0413

総合スコア8

Redux

Reduxは、JavaScriptアプリケーションの状態を管理するためのオープンソースライブラリです。ReactやAngularで一般的にユーザーインターフェイスの構築に利用されます。

React.js

Reactは、アプリケーションのインターフェースを構築するためのオープンソースJavaScriptライブラリです。

0グッド

0クリップ

投稿2021/01/06 16:37

編集2021/01/11 11:36

##実現したいこと
タイトル通りですが、フォームにて登録する際、Nameの入力値が3文字以上の時にSAVEボタンが押せるが、
3文字未満の時はSAVEボタンが押せないようにしたいです。

##試したこと
以下のようにコードを書きました。
※React hookを使い、stateを持たせ、disabledをbuttonにセット、ボタンを押した時に入力値のチェックを行い、
OKだったら(入力値3文字以上の場合)ボタンがdisabledがfalseになることを想定し、書きました。
####Content.js

'use strict'; import React,{ useState } from 'react'; import { createStore } from "redux"; export default function Content(props) { //NAME3文字以上ででsaveボタン押せるようにdisabledを入れる const [checkName, setCheckName] = useState(true); console.log(props) return ( <> <p>content</p> ​ <Form onChangeId={props.onChangeId} onChangeName={props.onChangeName} setCheckName={setCheckName} /> <button type="button" className="btn btn-outline-success" onClick={(e) => {props.onSaveItem();console.log(e)}} disabled = {checkName} > SAVE </button> </> ); } function Form(props) { return ( <form> <div className="form-group"> <label className="pt-2">ID</label> <input id="id" type="text" className="form-control" value={props.id} onChange={(e) => props.onChangeId(e)} /> <label className="pt-2">Name</label> <input id="name" type="text" className="form-control" value={props.name} onChange={ (e) => { if (e.target.value.length >= 3){ setCheckName(false) {props.onChangeName(e)} } } } /> </div> </form> ) }

####App.js

import React, { Component } from 'react'; import Aside from './components/Aside'; import Header from './components/Header'; import Footer from './components/Footer'; import Top from './components/Top'; import Content from './components/Content'; class App extends Component { constructor(props) { super(props); } // formId onChangeId = (e) => { console.log(e.target.value) this.props.updateFormId(e.target.value); } // forName onChangeName = (e) => { console.log(e.target.value) this.props.updateFormName(e.target.value); } // clickSaveButton onSaveItem = () => { console.log(this.props) console.log("SAVE_ID: ",this.props.id); console.log("SAVE_NAME: ",this.props.name); } render() { const formItem = this.props // formEvent const contentHandler = ({onChangeId, onChangeName, onSaveItem}) => ({ onChangeId, onChangeName, onSaveItem }) console.log('App.render:', this.props); return ( <div className="content"> <Aside /> <Header /> sample site {/* <div className="row"> <div className="col-5"> <p>test</p> </div> </div> */} <Top /> <br /> <br /> <br /> <div className="container"> <Content formItem={formItem} {...contentHandler(this)} /> </div> <br /> <br /> <br /> <Footer /> </div> ); } } export default App;

##結果
NGパターンでもOKパターンでもSAVEボタンを押してもボタンが機能せず、setCheckNameが参照されないエラーが出ています。
※disabled機能を追加する前まではSAVEボタンにて、ログが出せていた。(Reduxを使用)
解決方法見つけられず、、、
何か、ヒントいただけるとありがたいです。

また、必要な情報があれば追記します。よろしくお願いします。

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

hoshi-takanori

2021/01/06 17:43

えっと、まず入力値を state として持つ必要があるのでは…。
yuki-o0413

2021/01/07 00:17

助言ありがとうございます。stateを持たせて処理するやり方を取り入れてみます。
hoshi-takanori

2021/01/10 10:05

なかなか苦労しているようですね。質問ですが、 ・Content を使ってる側のコードはどうなってますか? (onSaveItem はともかく、onChangeId や onChangeName を Content の props にする必要性が見えないので…。) ・Redux 本当に使ってますか?
yuki-o0413

2021/01/10 10:44 編集

はい、、、どハマりしてしまっています。追記ありがとうございます。 以下のGithubにて管理しています(フォームやバリデーションを実装前のもの) https://github.com/yuki-o0413/ReduxTutorial/tree/makeValidation 自分ではいろいろ調べ、Reduxを使用してlogが出力されるよう作ったつもりでいますが、もし問題点があるのなら改善したいです。App.jsの情報を追記しますね。
hoshi-takanori

2021/01/10 11:28

リポジトリ拝見しましたが、 ・docker を使ってらっしゃるようですが、あまり必要性が見えません。 ・redux は、使ったつもりになっていらっしゃるようですが、残念ながら使えてない気がします。 ・そもそもこれって何かの教材をベースとしてるのでしょうか?
yuki-o0413

2021/01/10 13:54

そうでしたか。使えてなかったんですね、、、 教材は辞書がわりに使い、基本的には自己調査と人からアドバイスをもらいながらやっていました。 コード見ていただきありがとうございました。ビルドから見直す必要がありそうですね。
hoshi-takanori

2021/01/10 14:11

スクールなどに通ってるわけではなく、独学ってことですね。 よかったら今回のコードが動くまでサポートしましょうか? 最初は docker も redux も使わない方向になりますが。
yuki-o0413

2021/01/10 16:36

それはありがたいです! ぜひお願いしたいです。
hoshi-takanori

2021/01/10 17:01

それでは環境構築からですね。以下の質問に答えてもらっていいですか? ・お使いのパソコンは Mac でしょうか? ・Mac の場合、Homebrew というのを使ったことはありますか? ・パソコンに直接 node を入れてますか? 入れてる場合、その方法は? ・node のパッケージマネージャは npm と yarn どちらにしましょう? (個人的には yarn がお勧め。) ・TypeScript を使ったことはありますか? あるいは、Java などの型のある言語は? ・今回 id や name を入力して、その先はサーバーと通信する予定ですか?
yuki-o0413

2021/01/11 03:16

ありがとうございます! ・お使いのパソコンは Mac でしょうか? →MacBook Air使ってます ・Mac の場合、Homebrew というのを使ったことはありますか? →はいあります。https://brew.sh/index_jaを参考にしました ・パソコンに直接 node を入れてますか? 入れてる場合、その方法は? →はいローカルに入れています。%brew install nodejsで入れました。 ・node のパッケージマネージャは npm と yarn どちらにしましょう? (個人的には yarn がお勧め。) →npmの方がより使ってましたが、yarnでやってみたいです ・TypeScript を使ったことはありますか? あるいは、Java などの型のある言語は? →TypeScriptは使ったことがありません。Javaは以前文法を独学していました。コーディングは不慣れですが、JSよりは理解度が高いです。 ・今回 id や name を入力して、その先はサーバーと通信する予定ですか? →いえそこまではまだ考えていませんでした。とりあえずsaveボタンを押すとform情報がポップアップされるまでを練習しようと思っていました。
hoshi-takanori

2021/01/11 04:03

Homebrew は既にお使いなんですね。すばらしい。まず Homebrew (brew) の注意事項ですが、 ・brew の前にお使いのシェルと PATH を確認しましょう。echo $SHELL および echo $PATH の結果を教えてください。 ・brew は /usr/local (M1 Mac なら /opt/homebrew かも) にいろんなファイルが入りますので、この中を自分でいじるのはあまりよくありません。PATH に /usr/local/bin が含まれていることを確認しましょう。 ・brew は自分のユーザー権限で扱います。たまに sudo しろという記事があったりしますが、基本的には sudo の必要はないはず。sudo は使ったことありますか? ・brew install 以外でよく使うコマンドは、brew ls, brew update および brew upgrade でしょうか。これらを使ったことはありますか? ・とりあえず brew で yarn を入れましょう。また、brew で既に入ってるものも最新版に更新して、node と yarn のバージョンヲ確認してください。それぞれ node -v および yarn -v です。 今までのところでわからない言葉があったら遠慮なく聞いてください。予想としてはシェル、PATH、/usr/local、ユーザー権限、sudo、brew update と brew upgrade の違いあたりでしょうか。
yuki-o0413

2021/01/11 11:22

・echo $SHELL および echo $PATH の結果を教えてください。 →echo $SHELLは「/bin/zsh」で、echo $PATHは 「/usr/local/opt/mysql@5.7/bin:/Users/名前/.rbenv/shims:/Users/名前/.rbenv/shims:/usr/local/bin:/Users/名前/.pyenv/shims:/Users/名前/.pyenv/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin」 (/usr/local/bin含まれていました) >sudo は使ったことありますか? →はい、以前にあります(ユーザー権限はなるべく使わない方が良いものだと思っていました)。 >とりあえず brew で yarn を入れましょう →両方ともすでに使っていて、node -vは「v12.19.0」、yarn -vは「1.22.10」でした。 >brew ls, brew update および brew upgrade でしょうか。これらを使ったことはありますか? →はい、環境構築に嵌っていた時に使った覚えがあります。brew update と brew upgrade の違いはアップデートのみを行うことと、インストールしたパッケージのアップデートをするかの違いですね(今Qiitaで調べました!) >今までのところでわからない言葉があったら遠慮なく聞いてください。 →勉強になります!ありがとうございます。 Macユーザーになって半年くらいなので(以前はずっとWindowsでした)、PATHを調べた時にすごく長くて驚きました。ホームディレクトリなのに、なぜこんなに長いPathなのでしょうか?
hoshi-takanori

2021/01/11 12:08

最近 Mac ユーザーになったんですね。Welcome to Macintosh! > →echo $SHELLは「/bin/zsh」 最近の macOS はデフォルトのシェルが zsh になっています。しばらく前までは bash が使われていて、Linux でも bash を使ってる人も多いです。zsh も bash も似たような感じで使えますが、シェルの設定ファイルの名前が違います。zsh は ~/.zshrc ですが、bash は ~/.bashrc や ~/.bash_profile です。ネットの記事でシェルの設定を変更する記述がある場合、必要に応じて設定ファイルを読み替えてください。 echo $PATH で表示されるのは PATH という名前の環境変数ですが、これはホームディレクトリではなくて、コマンドを実行する時に、そのコマンドを実行する場所を設定するものです。ruby や python を使っていた形跡がありますね。試しに where ruby や where python3 を実行したら複数表示されるかも。 また、ホームディレクトリを設定する環境変数は $HOME で、値は /Users/名前 のはずです。 sudo はそうですね、Mac を一人で使っている分にはあまり使わなくていいはずですが、サーバーで作業する場合には必要になることもあるでしょう。 node と yarn は最新版をお使いですね。node (や ruby や python) のバージョンですが、サーバー系の人は nvm などを使って node のバージョンもきっちり管理したがるようです。node で直接サーバーを動かす場合、やはりその方が安心なのでしょう。React など、node をフロントエンドで使う人は、node そのもののバージョンにはこだわらず最新版を使ってる気がします。ビルドして bundle.js にしてしまえば、あとは node は関係なくなりますからね。 brew update と brew upgrade の違いは、brew update は単に brew でインストール可能なものとそのバージョンのリストを更新するだけで、brew upgrade で実際に更新を行うことになります。brew のパッケージはこまめに更新されるので、brew update や brew upgrade は定期的に行うことをお勧めします。また、git の最新版や、tree や jq などの便利コマンドを入れておくといいかもしれません。 さて、環境はだいたい確認できたので、React のプロジェクトを作りましょうか。create-react-app というコマンドを使う方法と、自分でパッケージを一つ一つ入れていく方法がありますが、どっちにしましょう?
yuki-o0413

2021/01/13 15:43

解説ありがとうございます。基礎がないのでツールの扱い方について教えてもらえるととても勉強になります!   create-react-appの方でやりたいです。自分でパッケージを入れていくもの勉強になると思いますが、チュートリアルで環境構築にえらく苦しんだので。
hoshi-takanori

2021/01/14 11:56

ツールの扱い方、そうですね。たとえば ls と ls -a の違いとか、ls -a ~ で表示されるいろんなものとか、.gitignore の設定とかでしょうか…。 create-react-app は yarn (yarn global add create-react-app) か npm (npm install -g create-react-app) でグローバルに入れます。使い方は、TypeScript のプロジェクトを生成する場合は、 create-react-app アプリ名 --template typescript になります。 アプリができたら、中に入って中身を確認しましょう。ls -aF (ls -alF の方がいいかも) すると ./ ../ .git/ .gitignore README.md node_modules/ package.json public/ src/ tsconfig.json yarn.lock となってます。また、tree src (tree コマンドはおすすめですが、.git や node_modules に対して使うと大変なことになります) すると src ├── App.css ├── App.test.tsx ├── App.tsx ├── index.css ├── index.tsx ├── logo.svg ├── react-app-env.d.ts ├── reportWebVitals.ts └── setupTests.ts こんな感じですね。 create-react-app で作った JavaScript と TypeScript のプロジェクトの違いは、 ・package.json に typescript (TypeScript コンパイラ本体) と @types/〜 という TypeScript 用の型定義パッケージがいくつか追加 (ライブラリによっては自分で TypeScript の型定義を持つものもありますが、もともと JavaScriipt で書かれたものは型定義がなくて、別のパッケージとして提供される場合があります。) ・tsconfig.json という、TypeScript の設定ファイルが生成 ・src の中身は、拡張子が .js から .ts または .tsx に変更 (JavaScript では JSX 記法を含むかどうかを拡張子で区別しませんが、TypeScript の場合 JSX 記法を含むものは拡張子を .tsx にする必要があります。) あと、デフォルトではパッケージ管理には yarn が使われ、yarn.lock ファイルができます。(npm の場合は package-lock.json です。普通はどっちか片方だと思う…。) なお、この yarn.lock ファイルがあれば node_modules ディレクトリの中身は (https://www.npmjs.com/ が生きてれば) まったく同じものが再現できるはずなので、yarn.lock ファイルは git で管理して、node_modules ディレクトリは管理しないのが一般的だと思います。(あえて管理する場合もありますが。) この状態で yarn start するとビルドされたものがブラウザで開きますが、最近の create-react-app で作ったものは .eslintcache というファイルができるようです。これを間違って git の管理対象にしてしまうと無駄な変更が発生して面倒くさいので、.gitignore に .eslintcache と書いておくことをお勧めします。また、src/index.tsx や src/App.tsx には余計なコードが色々書いてあるので、これらをすっきりさせた状態で一度 git commit すると良いでしょう。 とりあえず今回はこんなところで。次回は state や制御されたコンポーネントの話をする予定です。
yuki-o0413

2021/01/15 00:21

ありがとうございます!細かなTipsや知識まで教えていただき、本当に勉強になります!! TypeScriptはずっと気になっていたので、TypeScriptプロジェクトで生成してみますね。 >.gitignoreに .eslintcache と書いておくことをお勧めします。 →.gitignoreは使った事がなかったです。git管理対象にすると都合が悪くなるケースがあるんですね。。。 業後に教えていただいところまでやってみますね。
yuki-o0413

2021/01/15 12:23

①create-react-app アプリ名 --template typescriptにて新規プロジェクト作成 ②.gitignore に .eslintcache と書く ③GitHubリポジトリへプッシュ まで完了しました。 ▼以下リポジトリ https://github.com/yuki-o0413/ReactTypeLsn
hoshi-takanori

2021/01/15 13:49

お、すばらしい。ちゃんと動きますね。さて、ここからどうしましょう…。 とりあえず src/Content.tsx に質問文と同じ内容をコピーして、src/App.tsx からそれを利用するようにしてみますか? App.tsx の内容はお好みですが、とりあえず Content.tsx を動かすための最低限の内容でいいかと。また、TypeScript なのでいろいろエラーが出ますが、動かなくてもいいのでできるところまでやってみてください。
yuki-o0413

2021/01/15 14:42

ありがとうございます! TypeScriptだといろいろエラーが出てしまうんですか・・・。JSだからでしょうか? とりあえず、できるところまでやってみますね!
hoshi-takanori

2021/01/15 15:11

例えば function Content や Form の引数 props の型が分からないというエラーになると思います。 とりあえず props の後ろに : any をつける (props: any) とコンパイルは通りますが、any を使うのは良くない (というか TypeScript を使う意味がない) ので…。
yuki-o0413

2021/01/16 00:54 編集

propsにanyをつけるなんて技があるとは知りませんでした。なるほど。 とりあえず、どこまで写したら良いのか迷ったものの最低限必要そうなものをコピペしました。 おっしゃる通り、propsのところにエラーが出ました。よろしくお願いします。 ▼以下リポジトリ https://github.com/yuki-o0413/ReactTypeLsn
hoshi-takanori

2021/01/16 04:21 編集

やっぱり型の話をしないとですね。まず基本的な型 ・string (文字列) ・number (数値、整数と浮動小数点数の区別はない) ・boolean (真偽値、true または false) と特殊な型 ・any (何でも受け入れる型、JavaScript ではすべて any) ・null と undefined (値がないことを表す値、歴史的な事情で null と undefined は別) ・void (関数の戻り値がないことを表す型) があります。 また、複合的な型として、 ・配列 ([] をつける、string[], number[] など) ・オブジェクト ( { key: value} なやつ) ・union type (a と b が型の時、a | b は a または b という型になります) ・関数型 ((引数) => 戻り値の型) があります。 うーん、割と複雑ですね。詳しい説明はこちら。 https://typescript-jp.gitbook.io/deep-dive/type-system で、型を書ける場所は ・変数宣言   const x: number = 1; ・関数の引数と戻り値   function add(a: number, b: number): number { return a + b; } ・オブジェクトの中身 あたりですが、多くの場合 TypeScript のコンパイラが型を推論してくれるので、実際に書く必要がある場所は Java などと比べるとだいぶ少なくなります。 今回の Content.tsx の場合、まず props ですね。Form 関数から見ていきましょう。前に書いたように function Form(props: any) { ... } と書けばコンパイラは黙りますが、any を使うと型チェックをしなくなるので、コンパイルは通っても動かないコードになります。ので、できるだけ any は使わないことをお勧めします。(とはいえ、動くと分かっている JavaScript のコードをとりあえず TypeScript で動かすために使うこともあります。) で、Form の引数 props の型ですが、props がどう使われているかを見る必要があります。まず、props の型を {} にしてみましょう。これは空のオブジェクトを表す型になります。 function Form(props: {}) { ... } Form の中の props.id, props.onChangeId などがエラーになると思います。(Visual Studio Code だと赤の下線が引かれると思いますが、そういえば VSCode 使ってましたっけ?) props は id や onChangeId などのプロパティを持つオブジェクトです。オブジェクトの型宣言は props の後ろに直接書く方法 function Form(props: { id: string, onChangeId: (e: any) => void }) { ... } もありますが、長くなるので、interface を使って名前をつけると良いでしょう。 (なお、コメント欄だとスペースが消えるので、全角スペースでインデントしましたが、そのままコピペするとエラーになるので、半角スペースに置き換えてください。) interface FormProps {  id: string,  onChangeId: (e: React.ChangeEvent<HTMLInputElement>) => void, } function Form(props: FormProps) { ... } これで props.id と props.onChangeId のエラーが消えるはずです。props.id の型が文字列なのはいいと思いますが、props.onChangeId の型は引数が一つで戻り値なしの関数になります。引数は「引数名: 型」という風に書きます。引数 e の型は React.ChangeEvent<HTMLInputElement> というもので、React が用意しているイベント引数の型になります。 まだ props.name と props.onChangeName のエラーが残ってると思いますので、FormProps にこれらの型を追加しましょう。また、setCheckName もエラーになってると思いますが、たぶんこれは props. setCheckName の間違いだとおもいますので、これも FormProps に追加すると良いでしょう。このように TypeScript を使うとコンパイラがエラーを見つけてくれます。 さて、Form の引数の型を定義し終わったら、次は Content の引数 props ですが、その前に Content の中で Form を使ってる場所 (return の <Form ... />) に注目しましょう。Form がエラーになってると思います。VSCode なら Form にカーソルを合わせると Type '{ 略 }' is missing the following properties from type 'FormProps': id, name のようなエラーが表示されると思いますが、これは FormProps で定義された id や name が指定されてないということで、ここでもエラーを指摘してくれています。 で、Content の引数 props について考えると、onChangeId や onChangeName と合わせて、id と name を親コンポーネント (App) から受け取って Form に渡しているはずなので、それを考えて ContentProps の型を定義して、<Form ... /> のプロパティも追加してあげればいいでしょう。 そんな感じで、TypeScript の型は、最初は面倒臭いと思いますが、ちゃんと使えばコンパイル時にエラーを見つけてくれて、実行時に訳の分からないエラーが出ることが少なくなります。ふぅ。
yuki-o0413

2021/01/16 04:56

>うーん、割と複雑ですね。詳しい説明はこちら。 >https://typescript-jp.gitbook.io/deep-dive/type-system →面白い!これがTypeScriptなんですね!言語名の”Type”の意味が分かった気がします。言語はJavaから入っているので、JSよりしっくりくる気がします。 >そういえば VSCode 使ってましたっけ? →はい!VSCodeです。 丁寧なご説明ありがとうございます!とりあえずご指導いただいた部分やってみます。わからなくなったところがあればまた質問させてください!
hoshi-takanori

2021/01/16 05:15

面白いと思えるのは良いことです。TypeScript の型システムは Java よりも柔軟性があって、リテラル型と union type を組み合わせて var x: 1 | 'a' | true なんてこともできます。特に React や Redux を使う上では複雑な型計算 (と言っても自分で書く必要はあまりなくて、ライブラリがつけてくれる型に合わせることがほとんどですが) によってエラーがないことが保障されるので、型のない JavaScript で書くのが怖くなります。
yuki-o0413

2021/01/16 06:52

FormPropsの型の指定の仕方でお聞きしたいのですが、”setCheckName”の型の指定をどうするかで迷っています。「setCheckName():boolean」と書いてみたのですが、エラーが消えず。。。 Contentの引数propsの方でもエラーが消えないです。一度コードをみていただきたく ``` 'use strict'; import React,{ useState } from 'react'; // import { createStore } from "redux"; interface ContentProps { id: string, onChangeId: (e: React.ChangeEvent<HTMLInputElement>) => void, onChangeName: (e: React.ChangeEvent<HTMLInputElement>) => void, onSaveItem(): void } export default function Content(props:ContentProps) { //NAME3文字以上ででsaveボタン押せるようにdisabledを入れる const [checkName, setCheckName] = useState(true); console.log(props) return ( <> <p>content</p> <Form onChangeId={props.onChangeId} onChangeName={props.onChangeName} setCheckName={setCheckName} /> <button type="button" className="btn btn-outline-success" onClick={(e) => {props.onSaveItem();console.log(e)}} disabled = {checkName} > SAVE </button> </> ); } interface FormProps { id: string, onChangeId: (e: React.ChangeEvent<HTMLInputElement>) => void, onChangeName: (e: React.ChangeEvent<HTMLInputElement>) => void, name: string, setCheckName():boolean } function Form(props:FormProps) { return ( <form> <div className="form-group"> <label className="pt-2">ID</label> <input id="id" type="text" className="form-control" value={props.id} onChange={(e) => props.onChangeId(e)} /> <label className="pt-2">Name</label> <input id="name" type="text" className="form-control" value={props.name} onChange={ (e) => { if (e.target.value.length >= 3){ props.setCheckName(false) {props.onChangeName(e)} } } } /> </div> </form> ) } ``` よろしくお願いします。 https://github.com/yuki-o0413/ReactTypeLsn/tree/master/src
hoshi-takanori

2021/01/16 09:54

setCheckName の型ですが、props.setCheckName(false) として使っているので、これは boolean の引数を取って戻り値のない関数になります。ので setCheckName: (value: boolean) => void になります。あ、 setCheckName(value: boolean): void とも書けますね。どちらにしても、引数の名前 (value にしましたが、何でも構いません) は適当につけてあげる必要があります。 また、Content の中の <Form ... /> に id と name を渡してあげる必要がありますね。とりあえず <Form id='' name='' (以下略) とすればエラーは消えると思うので、App.tsx をどうするか考えてみてください。
yuki-o0413

2021/01/16 09:59

>setCheckName の型ですが、props.setCheckName(false) として使っているので、これは boolean の引数を取って戻り値のない関数になります。 →なるほどです。一度引数をとってから型を指定する必要があるんですね。書き方に慣れるようにします。 ありがとうございます!やってみます。
hoshi-takanori

2021/01/16 10:18

そういえば、以前のソース (ReduxTutorial のやつ) を確認したら、App.js がふたつあって、もう一つの方で connect してたのを見落としてました。ごめんなさい。ということは一応 redux が使われてはいたけど、使い方が謎だったことになります。そして、state 管理の方法がいろいろある話をする必要がありますね。
yuki-o0413

2021/01/16 10:56 編集

実は今さっき確認したのですが、「setCheckName もエラーになってると思いますが、たぶんこれは props. setCheckName の間違いだとおもいますので」というのを受けて、なるほどと思い、ReduxTutorial の方に"props" 付け加えたら、バリデーション3文字未満で効いたんです! ただ、一回テキストボックス内で3文字以上を入力してから(ボタンを押す前に)2文字以下減らしてボタン押下した場合はボタンのdisabledが解除されてしまって・・・。 これはRedux機能を使いこなせなかったからなのでしょうか? >state 管理の方法がいろいろある話をする必要がありますね。 →是非ともお願いしたいです! また、TypeScriptの方のプロジェクトですが、hoshi様からご教授いただいたように、定義したところ、Formのエラーは解除できました! App.jsの方はとりあえず過去のコードを入れてみたのですが、エラーばかりで(Form.jsのコードがうまく読み込まれていない?)。 以下コードです。お願いします! https://github.com/yuki-o0413/ReactTypeLsn/blob/master/src/App.tsx
hoshi-takanori

2021/01/16 12:59

App.tsx 見ましたが、関数コンポーネントなのにクラスコンポーネントの書き方や redux を前提としたコード混ざってるのでおかしなことになってますね。基本的に関数コンポーネントで行くってことでいいですよね。redux はどうしよう…。 とりあえず、暫定的にエラーを潰したやつが以下になります。ただし入力するとエラーになります。 https://github.com/hoshi-takanori/ReactTypeLsn/blob/fork-1/src/App.tsx https://github.com/hoshi-takanori/ReactTypeLsn/compare/fork-0...fork-1 で、state をどう管理するかでここから分岐しますが、とりあえず App がローカルに state を持つようにしたのが次のやつです。(Content.tsx もちょっといじりました。) https://github.com/hoshi-takanori/ReactTypeLsn/blob/fork-2/src/App.tsx https://github.com/hoshi-takanori/ReactTypeLsn/compare/fork-1...fork-2
hoshi-takanori

2021/01/16 13:29 編集

質問文のコードは redux を使っていて、もう一つの App.js https://github.com/yuki-o0413/ReduxTutorial/blob/makeValidation/app/src/sample/containers/App.js の中で、id と name は mapStateToProps、updateFormId と updateFormName は mapDispatchToProps によってそれぞれ与えられていました。 これをいったん今回の App.tsx では App コンポーネントのローカルな state としています。 const [id, updateFormId] = useState(''); const [name, updateFormName] = useState(''); これで id と name がローカルな state、updateFormId と updateFormName はそれを更新する関数となります。で、onChangeId と onChangeName はイベント引数 e から入力値を取り出して state を更新する関数で、それを Content 経由で Form に渡していることになります。この辺は大丈夫でしょうか? また、Form コンポーネントには入力欄がふたつありますが、id のほうは「制御されたコンポーネント」になってます。 https://ja.reactjs.org/docs/forms.html 制御されたコンポーネントというのは、ユーザーが入力するたびに onChangeId が呼ばれて、その中で App コンポーネントの updateFormId が実行されて App の state である id の値が更新され、その結果 Content や Form の props (の中の id の値) も更新されるので最終的に入力内容が更新されます。ところが、途中の Content に <Form id='' ... /> と書いてあると id の値の伝搬がここで止まってしまい、入力しても表示が更新されないという事態が起こってました。 そして、name の入力欄は「中途半端に制御されたコンポーネント」になっています。これについてはまたのちほど。 また、親から子、子から孫へと props を受け渡すのは「props のバケツリレー」と呼ばれていて、面倒だし途中で受け渡し損ねたりする事故を防ぐというのが redux を入れる動機になります。 https://tech.playground.style/javascript/state-management/
hoshi-takanori

2021/01/16 14:02

それから、git で fork されたものを扱う方法はご存知ですか? 別の場所に clone してもいいのですが、同じリポジトリに取り込むこともできます。 まず、現在の状態を確認して % git remote -v origin https://github.com/yuki-o0413/ReactTypeLsn.git (fetch) origin https://github.com/yuki-o0413/ReactTypeLsn.git (push) % git branch -avv * master 3995335 [origin/master] added old file to App.tsx remotes/origin/master 3995335 added old file to App.tsx 新しい remote リポジトリを追加します。とりあえず名前は fork としてみました。 % git remote add fork https://github.com/hoshi-takanori/ReactTypeLsn.git % git remote -v fork https://github.com/hoshi-takanori/ReactTypeLsn.git (fetch) fork https://github.com/hoshi-takanori/ReactTypeLsn.git (push) origin https://github.com/yuki-o0413/ReactTypeLsn.git (fetch) origin https://github.com/yuki-o0413/ReactTypeLsn.git (push) fork リポジトリの内容を取り込むには git fetch します。 % git fetch fork (いろいろ表示される) % git branch -avv * master 3995335 [origin/master] added old file to App.tsx remotes/fork/fork-0 3995335 added old file to App.tsx remotes/fork/fork-1 06e0840 とりあえずエラーは潰したけど、入力するとエラー remotes/fork/fork-2 5264b7c とりあえず動く、けどいろいろ納得いかない remotes/fork/master 3995335 added old file to App.tsx remotes/origin/master 3995335 added old file to App.tsx fork リポジトリの内容をチェックアウトするには、まず git status で現在の変更内容を確認して、コミットしてない修正があれば commit するか stash するか restore しておきましょう。 % git checkout fork-2 Branch 'fork-2' set up to track remote branch 'fork-2' from 'fork'. Switched to a new branch 'fork-2' % git branch -avv * fork-2 5264b7c [fork/fork-2] とりあえず動く、けどいろいろ納得いかない master 3995335 [origin/master] added old file to App.tsx remotes/fork/fork-0 3995335 added old file to App.tsx remotes/fork/fork-1 06e0840 とりあえずエラーは潰したけど、入力するとエラー remotes/fork/fork-2 5264b7c とりあえず動く、けどいろいろ納得いかない remotes/fork/master 3995335 added old file to App.tsx remotes/origin/master 3995335 added old file to App.tsx こんな感じで行けるはず。git を使う場合、ちゃんと変更内容を commit していれば気軽にブランチを切り替えることができます。
yuki-o0413

2021/01/17 05:46

順序立てて、解説していただいているので、すごくわかりやすいです! とりあえず教えていただいたところまでAppとContentを修正し、プッシュしました https://github.com/yuki-o0413/ReactTypeLsn/tree/master/src >onChangeId と onChangeName はイベント引数 e から入力値を取り出して state を更新する関数で、それを Content 経由で Form に渡していることになります。この辺は大丈夫でしょうか? →はい、ここまでは大丈夫です。 >id のほうは「制御されたコンポーネント」 →この部分の理解が少し追いついてなくて、、、 表示を確認すると、idの部分だけ入力してもテキストボックスに表示されなかったのですが、この制御されたコンポーネントというのが関係しているのでしょうか? >git で fork →使った事なかったです!概念は理解できたのですが、教えていただいたコマンドを入力し、iterm上では表示確認できたのですが、自身のGIthubにどう反映されるのか確認の仕方がわからず調査中です。使いこなせるまでには時間必要かもです。
hoshi-takanori

2021/01/17 05:59

git の件はご参考までということで、すぐに使えなくても大丈夫です。ブランチの戻し方を書くのを忘れてましたが、git checkout master でご自分のブランチに戻ります。 > idの部分だけ入力してもテキストボックスに表示されなかった のは、この部分のせいですね。 https://github.com/yuki-o0413/ReactTypeLsn/blob/master/src/App.tsx#L35 これを id={id} にすれば入力できるようになると思います。 詳しい説明はまた後ほど。
yuki-o0413

2021/01/17 06:28

ありがとうございます!! stateで定義した初期値をConptentに渡せてなかったからだったんですね!修正したら、入力もログが出力されるもの確認できました!
hoshi-takanori

2021/01/17 08:17

まず、制御されたコンポーネントについて。 id={id} は初期値だけでなく、id 入力欄に入力があるたびに id の値が更新され、その値が input 要素に再度反映されます。これによって常に id の値と入力内容が同期していることが保障されるため、onSaveItem で updateFormId(''); すれば入力欄のクリアが簡単に実装できたり、例えば onChangeId で id の値をいじることで入力できる文字の種類や長さ制限したりすることもできます。 (ただ、id の値をコードから変更すると、入力欄のカーソル位置や、日本語を変換中の値はどうなるかなどの問題が出てきそうな気がしますし、単純に入力値を保持するだけであればそこまで制御する必要はないのではないかという意見もあるようです。→ https://blog.ojisan.io/react-form-control/ ) とはいえ、React の公式ドキュメントで制御されたコンポーネントが推奨されてますので、それに従えばいいのではないかと思ってます。 一方、name は <input> タグで defaultValue={props.name} と変更しましたので、こちらは制御されてないコンポーネント になってます。入力欄に 3 文字以上入力した場合には onChangeName が呼ばれて name が更新されますが、onChangeName や onSaveItem で name の値を変更しても、それが入力欄に反映されることはありません。 自分は name に関しても <input> タグを value={props.name} に戻して制御されたコンポーネントにするのがいいと思いますが、単純にそうすると 3 文字以上入力しないと name が変更されないバリデーションがかかっているために onChangeName が呼ばれず、空欄の状態から 1 文字ずつ入力することができません。(3 文字以上コピーしてペーストすれば更新されますが、今度は 3 文字より減らすことができなくなります。) 具体的には、defaultValue={props.name} の場合、 ・最初 → name = '' が defaultValue={props.name} として渡るため、入力欄は空 ・入力欄に a と打つ → 入力欄は a になるが、バリデーションによって onChangeName が呼ばれず、name は '' のまま ・入力欄に b と打つ → 入力欄は ab になるが、バリデーションによって onChangeName が呼ばれず、name は '' のまま ・入力欄に c と打つ → 入力欄は abc になり、バリデーション条件を満たすので onChangeName が呼ばれ、name は 'abc' になる ・入力欄をクリア → 入力欄は空になるが、バリデーションによって onChangeName が呼ばれず、name は 'abc' のまま value={props.name} の場合、 ・最初 → name = '' なので、入力欄は空 ・入力欄に a と打つ → 入力欄は一瞬 a になるが、バリデーションによって onChangeName が呼ばれず、name は '' のままで入力欄が再描画され、空になる ・abc をコピーして入力欄にペースト → バリデーション条件を満たすので onChangeName が呼ばれ、name は 'abc' になり、それが再描画される ・入力欄をクリア → 入力欄は一瞬空になるが、バリデーションによって onChangeName が呼ばれず、name は 'abc' のままで入力欄が再描画され、abc になる
hoshi-takanori

2021/01/17 10:19

続きます。つまり、現状のバリデーションは、バリデーションに通ったら onChangeName を呼んで name を更新するけど、通らなければ onChangeName は呼ばず、name は元の値のままになってます。個人的にはこれは良くないと思ってます。 また、バリデーションを行う場所ですが、現在 Form コンポーネントで 3 文字以上という判定を行なっていて、汎用性に欠けるし、何よりも「3 文字以上」というビジネスロジックを入力コンポーネントが持ってしまうというのも良くないと思います。(Form に対して何文字以上とか文字種はこれとかの条件を外から設定して、From の中ではそれを満たすものしか入力させない、という方向性もありますが…。) というわけで、自分だったらこうするというのを書いてみました。diff だけで分かりますかね。 https://github.com/hoshi-takanori/ReactTypeLsn/compare/fork-3...fork-4 ・バリデーションは App で行うことにして、Content に対して checkName プロパティを渡すようにしました。また、バリデーションの結果は true が OK の方が自然かと思います。 ・Content では checkName を state からプロパティに変更して、false の場合にはボタンを押せないようにしました。 ・Form では setCheckName をなくして、name の入力時にも単純に onChangeName を呼ぶだけにしました。 その他の細かい修正として、 ・プロパティの順番を揃えました。(id, name そ先頭にしてみました。) ・ onChange={(e) => props.onChangeId(e)} というのは props.onChangeId をそのまま渡すのと同じなので、そうしました。(クラスコンポーネントで this.setState とかする場合は違う意味になる場合もありますが…。) この方法の良い点は、 ・id や name の値を実際に利用する場所 (onSaveItem) の近くにバリデーション条件を書ける。 ・id と name の条件を組み合わせたバリデーションも可能。(コメント参照) といったところでしょうか。
yuki-o0413

2021/01/17 13:27

バリデーションの条件を満たした場合はonChangeが呼ばれ、nameのログが出されるようになるけど、バリデーションの条件が満たされない状態になるとonChangeが呼ばれないから値の変更が起こらず、ログの内容が変わらなかったのですね。改めてブラウザ表示での、ログの出方を確認し、納得しました。 ロジックにそもそも欠陥があったんですね。。。 貴重な週末の時間にご教授いただき本当にありがとうございます! 考えてくださったコードも早速確認させていただき、動き確認してみますね!!本当になんとお礼を言ったら良いのか!!
yuki-o0413

2021/01/17 14:08

教えていただいたようにコードを修正&ブラウザでの動きも確認しました! まさに理想通りの動きになりました! 解説いただいた内容も眼から鱗のものばかりで(idもバリデーションつけた場合まで!) 元のJSのファイルにも導入してみますね!
yuki-o0413

2021/01/18 00:21 編集

教えていただいた、TSのコードを元に、JSの方のコードを直してみました。 修正後。バリデーションが想定通り効くようになりました!! よかったら、改めてコード見ていただいても良いでしょうか? ▼Github https://github.com/yuki-o0413/ReduxTutorial/commits/makeValidation/app/src/sample ▼Content.js ``` 'use strict'; import React,{ useState } from 'react'; import { createStore } from "redux"; export default function Content(props) { console.log(props) return ( <> <p>content</p> <Form id = {props.id} name = {props.name} onChangeId={props.onChangeId} onChangeName={props.onChangeName} /> <button type="button" className="btn btn-outline-success" onClick={(e) => {props.onSaveItem();console.log(e)}} disabled = {!props.checkName} > SAVE </button> </> ); } function Form(props) { return ( <form> <div className="form-group"> <label className="pt-2">ID</label> <input id="id" type="text" className="form-control" value={props.id} onChange={props.onChangeId} /> <label className="pt-2">Name</label> <input id="name" type="text" className="form-control" value={props.name} onChange={props.onChangeName} /> </div> </form> ) } ``` ▼App.js ``` import React, { Component } from 'react'; import Aside from './components/Aside'; import Header from './components/Header'; import Footer from './components/Footer'; import Top from './components/Top'; import Content from './components/Content'; class App extends Component { constructor(props) { super(props); } // formId onChangeId = (e) => { console.log(e.target.value) this.props.updateFormId(e.target.value); } // forName onChangeName = (e) => { console.log(e.target.value) this.props.updateFormName(e.target.value); } // clickSaveButton onSaveItem = () => { console.log(this.props) console.log("SAVE_ID: ",this.props.id); console.log("SAVE_NAME: ",this.props.name); } render() { const formItem = this.props // formEvent const contentHandler = ({onChangeId, onChangeName, onSaveItem}) => ({ onChangeId, onChangeName, onSaveItem }) console.log('App.render:', this.props); //NAME3文字以上ででsaveボタン押せるようにdisabledを入れる const checkName = this.props.name.length >=3; return ( <div className="content"> <Aside /> <Header /> sample site {/* <div className="row"> <div className="col-5"> <p>test</p> </div> </div> */} <Top /> <br /> <br /> <br /> <div className="container"> <Content formItem={formItem} {...contentHandler(this)} checkName={checkName} /> </div> <br /> <br /> <br /> <Footer /> </div> ); } } export default App; ```
hoshi-takanori

2021/01/18 20:20

App の (Redux からもらった) id と name の値が Content や Form に渡ってないですね。 Form が入力可能なのは、Form の props が変わってなくて再描画が行われてないからです。 App から formItem ってのを Content に渡してますが、これはあくまでも formItem というプロパティになってるので、id と name にばらして渡す必要があります。(というか、contentHandler が何をしてるのか分かってるのか疑問が…。) ところで、redux もやりますか?
yuki-o0413

2021/01/19 00:05

>App から formItem ってのを Content に渡してますが、これはあくまでも formItem というプロパティになってるので、id と name にばらして渡す必要があります。 →ご指摘ありがとうございます。課題達成できたと勘違いしてしまっていました・・・ そうなんですね!ご指摘いただいた点、idとnameの値をバラす部分から書き直してみます。 >(というか、contentHandler が何をしてるのか分かってるのか疑問が…。) →はい。。。単に処理をまとめているものだと思っていたのですが、違うみたいですね。。。 ところで、redux もやりますか? →はい!ぜひ、お願いしたいです!
hoshi-takanori

2021/01/19 00:46 編集

App から Content を利用している部分ですが、 <Content formItem={formItem} {...contentHandler(this)} checkName={checkName} /> formItem={formItem} ですが、たぶんこれで id と name を渡してるつもりだと思いますが、formItem の値は const formItem = this.props としているので this.props です。で、App の props ですが、まず index.js で <App /> としているので、index.js からは何も指定されてません。が、src/containers/App.js で export default connect(mapStateToProps, mapDispatchToProps)(App) としているので、Redux の state から id と name、それから action の updateFormId と updateFormName が渡ってるはずです。実際、console.log('App.render:', this.props); でそれらが表示されてます。(この辺は redux の話をするときにまたやるので、今はそれらが渡ってることだけ分かれば良いです。) で、formItem={formItem} と書いた場合、これは id と name を別々に渡してるわけではなく、formItem という一つのオブジェクトにまとめて渡していることになります。TypeScript だったら Content の props の型を次のように描く必要があるでしょう。 interface ContentProps {  formItem: { id: string, name: string },  // 略 } 次に contentHandler ですが、 const contentHandler = ({onChangeId, onChangeName, onSaveItem}) => ({ onChangeId, onChangeName, onSaveItem }) とした上で {...contentHandler(this)} してますが、どこでこんな書き方を覚えたのやら…。 まず、contentHandler は関数です。引数は一つのオブジェクトですが、それを onChangeId, onChangeName, onSaveItem という 3 つの変数に分割代入しています。そしてこの関数の戻り値は interface {  onChangeId: any,  onChangeName: any,  onSaveItem: any } という型のオブジェクトです。そして contentHandler(this) として呼び出してますが、この this は App コンポーネントのオブジェクトで、App コンポーネントには onChangeId, onChangeName, onSaveItem, render というメソッド (と、親クラス React.Component で定義された state プロパティやその他のメソッド) がありますが、結果的に onChangeId, onChangeName, onSaveItem の 3 つのメソッドだけを取り出して新しいオブジェクトを作ってます。 そして、<Content formItem={formItem} ... /> とした場合は formItem という名前のプロパティに一つのオブジェクトをそのまま渡してましたが、<Content {...contentHandler(this)} ... /> の場合は contentHandler(this) の結果のオブジェクトの各プロパティをばらして渡すことになります。contentHandler(this) の結果のオブジェクトは onChangeId, onChangeName, onSaveItem の 3 つのプロパティを持つので、これは結局 <Content  onChangeId={this.onChangeId}  onChangeName={this.onChangeName}  onSaveItem={this.onSaveItem} ... /> と書いたのと同じ結果になっています。そして、こっちの書き方の方が分かりやすいと思いますけど…。
hoshi-takanori

2021/01/19 02:26

参考リンク ・分割代入 https://ja.javascript.info/destructuring-assignment ・オブジェクトの分割代入 https://maku77.github.io/js/syntax/object-destructuring.html ・Reactコンポーネントでのスプレッド構文はよくよく考えたら気持ち悪くなかった https://qiita.com/xx2xyyy/items/f767bc1768656fd9b46f Redux ・たぶんこれが一番分かりやすいと思います React + Redux のフロー図解 https://qiita.com/mpyw/items/a816c6380219b1d5a3bf ・TypeScriptでReactをやるときは、小さいアプリでもReduxを最初から使ってもいいかもねというお話 https://future-architect.github.io/articles/20200501/
yuki-o0413

2021/01/20 00:42 編集

解説と資料ありがとうございます! JS・Reactの学習初めて3ヶ月ほどで、理解が曖昧なまま進めている部分が多かったので、改めてRedux、JSの文法を見直せて助かります! 資料と解説を読み込んだ上で、コードを書き直してみました。お時間ある時にみていただけたら!! ▼Github https://github.com/yuki-o0413/ReduxTutorial/commits/makeValidation/app/src/sample 変更点として ①AppにてformItem というプロパティだったのを、id と name にばらして渡す ②hoshi様からアドバイスいただいたように分割代入していたプロパティをバラしてAppからContentに渡すようにする ③Contentにて受け取ったプロパティをFormに渡るようにする です。ブラウザでの動作確認も行いました。よろしくお願いします。あと、Reduxの資料今週中何度も読み込んでおきます! ▼App.js ``` import React, { Component } from 'react'; import Aside from './components/Aside'; import Header from './components/Header'; import Footer from './components/Footer'; import Top from './components/Top'; import Content from './components/Content'; class App extends Component { constructor(props) { super(props); } // formId onChangeId = (e) => { console.log(e.target.value) this.props.updateFormId(e.target.value); } // forName onChangeName = (e) => { console.log(e.target.value) this.props.updateFormName(e.target.value); } // clickSaveButton onSaveItem = () => { console.log(this.props) console.log("SAVE_ID: ",this.props.id); console.log("SAVE_NAME: ",this.props.name); } render() { const id = this.props.id; const name = this.props.name; console.log('App.render:', this.props); //NAME3文字以上ででsaveボタン押せるようにdisabledを入れる const checkName = this.props.name.length >=3; return ( <div className="content"> <Aside /> <Header /> sample site <Top /> <div className="container"> <Content id={id} name={name} onChangeId={this.onChangeId} onChangeName={this.onChangeName} onSaveItem={this.onSaveItem} checkName={checkName} /> </div> <Footer /> </div> ); } } export default App; ``` ▼Content.js ``` 'use strict'; import React,{ useState } from 'react'; import { createStore } from "redux"; export default function Content(props) { return ( <> <p>content</p> <Form in={props.id} name={props.name} onChangeId={props.onChangeId} onChangeName={props.onChangeName} /> <button type="button" className="btn btn-outline-success" onClick={(e) => {props.onSaveItem();console.log(e)}} disabled = {!props.checkName} > SAVE </button> </> ); } function Form(props) { return ( <form> <div className="form-group"> <label className="pt-2">ID</label> <input id="id" type="text" className="form-control" value={props.id} onChange={props.onChangeId} /> <label className="pt-2">Name</label> <input id="name" type="text" className="form-control" value={props.name} onChange={props.onChangeName} /> </div> </form> ) } ```
hoshi-takanori

2021/01/20 23:25

変更確認しました。良いと思います。(でも、sample.bundle.js をコミットする必要は…。) 続きは ReactTypeLsn の方でいいですよね。で、Redux の前に、現状の state と props がどうなってるかを確認しておきましょう。 ・ReduxTutorial の方は、state (id と name) は Redux で管理され、それを App で受け取ってます。 ・ReactTypeLsn では、state は React Hooks の useState を使って App で管理しています。 ・どちらも、App から Content、Content から Form へと、state とそれを変更する関数、つまり id, name, onChangeId, onChangeName を props として順次受け渡していました。これをバケツリレーと呼んでます。 バケツリレーは必ずしも悪いわけではありませんが、まぁ面倒ですよね。また、例えばログイン状態のような、あちこちで使う (かつ、途中のコンポーネントはそんなこと気にしたくない) ものもあります。これを解決する一つの方法が Redux ですが、その前に、そもそもどのコンポーネントがどの state を必要とするかについて考えることも大事です。 というわけで、今回の修正は、必ずしもこうした方が良い、と言うつもりはありませんが、こういう考え方もあるよ、というものです。 https://github.com/hoshi-takanori/ReactTypeLsn/compare/fork-5...fork-6 変更内容は、 ・state (id と name) を Content で管理するようにしました。 ・App は state を持ちません。checkName はバリデーションを行う関数にして、onSaveItem で結果だけ受け取るようにしました。 ・Form から Content へは、onChangeId のようなイベントを引数に取る関数ではなく、setId や setName という、値を直接変更する件数を渡すようにしました。(Content は、Form の input で発生するイベントそのものには興味はなくて、編集された値が欲しいだけなので。) ・Form や Content の引数を、props としてまとめて受け取る代わりに、props の内容をそれぞれ分割代入で受け取るようにしました。これによって props. をいちいち書く必要がなくなり、コードがすっきりします。 という感じです。こんなふうに、各コンポーネントの役割分担を見直す (リファクタリングの一種) ことはよくあります。その際 props の中身を変更したり、関数の引数を見直したりするわけですが、JavaScript だと確実に見落としが発生してバグの元になりますが、TypeScript ならコンパイラが見つけてくれるので助かります。(ただし、checkName を boolean から関数に変更しても !checkName はエラーにならなかったりしますが…。)
yuki-o0413

2021/01/21 17:11

>続きは ReactTypeLsn の方でいいですよね。で、Redux の前に、現状の state と props がどうなってるかを確認しておきましょう。 →はい!TSバージョンのReduxの書き方も学んでみたいです! 修正いただいた部分、拝見しました。propsの代わりに分割代入を使って表現する書き方をみて、眼から鱗でした。こんな書き方もあったんですね! こっちの方が、何を受け渡ししている方わかりやすいし、すっきりして見えますね。 特に、onChangeIdやonChangeNameを作って処理を代入するよりReact-hookを活用してFormのonChange内に処理をそのまま書いてしまうやり方は処理の内容がAppファイルと見比べなくてもわかって良いですね。この方式、今後真似させてもらいたいと思いました(私の方のコードもこのように書き換えてみます!)。 型の定義もそのプロパティを使う関数コンポーネントで定義した方が、あちこち見比べなくてよくなりますね。勉強になります!
hoshi-takanori

2021/01/21 22:06

Redux を始める準備ができたら、必要なパッケージを入れて、actions.js と reducers.js と createStore.js の 3 つを、拡張子だけ .ts に変えてコミットしておいてください。まずはこれに型をつけるところから始めましょう。
yuki-o0413

2021/01/23 01:13

https://github.com/hoshi-takanori/ReactTypeLsn/compare/fork-5...fork-6 でhoshi様に教えていただいたコードに書き換えてみたところ、以下のエラー文が出てしまいました。 「If the 'react' package actually exposes this module, consider sending a pull request to amend」 調査したところ、 tsconfig.jsonに、 “noImplicitAny”: false, をつけるとか、「yarn add -D @types/react」で型をインストールするなどの情報があったのですが、うまくいかず、環境の違いからでしょうか? とりあえず元のコードのまま、必要なパッケージを入れ(参考:https://qiita.com/maecho/items/e85abecd999257c2ca8c)、actionsなどの3つのファイルをsrc下に作り、コミットしました。
hoshi-takanori

2021/01/27 19:48

お返事遅くなってごめんなさい。 > 以下のエラー文が出てしまいました。 これはどのリポジトリの話でしょうか? (ReactTypeLsn の方は変わってないように見えますが…。)
yuki-o0413

2021/01/30 13:04

ご連絡ありがとうございます! すみません、タイポだったようです。 エラーだった時のログを残してなかったので、再現しようと思って、書き換えたらうまく機能しました! 失礼しました。以下修正済みのものです▼ https://github.com/yuki-o0413/ReactTypeLsn 続き、Redux編是非ご教授いただきたいです。よろしくお願いします。
hoshi-takanori

2021/01/31 06:16 編集

まず、“noImplicitAny”: false ですが、これは TypeScript の型チェックを無効化してしまうので、あまり使わない方がいいと思います。で、まずはこれを true にして型エラーを修正します。 https://github.com/hoshi-takanori/ReactTypeLsn/compare/fork-7...fork-8 react-redux の import でエラーになっていたのは、react-redux の型情報が見当たらないからですね。JavaScript のライブラリには、型情報が別になってるものがありまして、react-redux の場合は @types/react-redux というものを別途入れる必要があります。redux には型情報が付属してますけどね あとは足りない型情報をつけていきます。まず、actions.ts の各 action creator の引数と戻り値に型を付けます。引数の id, name はどちらも string なのはいいですよね。戻り値の型ですが、type と payload に型を付けました。特に type の型に注目して欲しいのですが、type: 'UPDATE_ID' とか type: 'UPDATE_NAME' というのは、どちらも単なる文字列ではなく、'UPDATE_ID' や 'UPDATE_NAME' という具体的な値しか許さない型という意味です。これをリテラル型と言います。 https://typescript-jp.gitbook.io/deep-dive/type-system/literal-types https://qiita.com/ConquestArrow/items/a2a657ce19feaf12f82f さらに、type ActionType = UpdateFormId | UpdateFormName; というのは、union 型と言って、ActionType 型の値は UpdateFormId 型の値か、UpdateFormName 型の値のどちらかになるということです。特に、この 2 つの型はどちらも type と payload を持ちますが、type の型が異なりますので、type が 'UPDATE_ID' なら palyload の型は { id: string } だし、type が 'UPDATE_NAME' なら palyload の型は { name: string } ということになります。 https://typescript-jp.gitbook.io/deep-dive/type-system/discriminated-unions そして、reducers.ts では引数 action を ActionType 型としていますが、union 型の性質によって、case 'UPDATE_ID': や case 'UPDATE_NAME': の中では payload の型としてそれぞれ適切なものが選択され、action.payload.idaction.payload.name に安全にアクセスできることになります。
hoshi-takanori

2021/01/31 06:44

次に、実際に redux を使っていきます。基本的にもう coonnect (mapStateToProps や mapDispatchToProps) のことは忘れて、Hooks 関数 (useSelector および useDispatch) を使うのがお勧めです。 https://github.com/hoshi-takanori/ReactTypeLsn/compare/fork-8...fork-9 最初に State の型ですね。redux ではアプリケーションが持つ状態を一元管理しますが、そのすべての状態をまとめた型 ApplicationState が欲しいので、まず reducers.ts で部分状態である id と name の型をそれぞれ IdState, NameState と定義して、createStores.ts でそれらをまとめて ApplicationState を定義しています。今回は IdState, NameState いずれも単なる string ですが、将来もっと複雑な状態を扱う可能性を考えてそれぞれ型定義しました。また、ApplicationState は自動的に作れそうな気もしますが、ちょっとやり方がわからない…。 次に useSelector ですが、こいつは ApplicationState から部分状態を取り出す関数を渡すと、実際の ApplicationState の値から部分状態を取り出して返してくれます。また、useSelector が使われたコンポーネントに対して、その部分状態の値が変更された時には自動的に再レンダリング (重要!) してくれるようになります。 createStores.ts の getIdState および getNameState は ApplicationState から部分状態を取り出す関数になります。これらを使って const id = useSelector(getIdState); const name = useSelector(getNameState); とするとそれぞれ id, name の値を取り出すことができます。(getIdState や getNameState を定義せずに const id = useSelector((state: ApplicationState) -> state.id); const name = useSelector((state: ApplicationState) -> state.name); と書くこともできます。) 最後に、ApplicationState を更新するには const dispatch = useDispatch(); を使って、action creator を呼び出して作った action を dispatch に渡してあげます。 はっきり言って、redux 面倒くさいですよね。特に型を意識するとコードが増えますが、アプリケーションが複雑になった時には、型がないとやってられないと思います。
yuki-o0413

2021/02/01 10:54

ありがとうございます!お手本を確認しながら一つ一つやってみますね。
yuki-o0413

2021/02/05 01:01

時間がかかってしまいすみません! 教えていただいたコードで直し、想定どおり動くことを確認できました!! お時間いただき、綺麗なコーディングと詳しい解説いただいて本当にありがとうございました。 TypeScriptについて知見を得る良い機会になりましたし、 今まで曖昧な理解だった、reduxのStateや更新する方法について理解が深まりました!本当になんとお礼を言ったら良いのか!
yuki-o0413

2021/02/08 00:31

表題の問題が解決しましたし、ベストアンサーにさせてもらいたいのですが、 回答の方に何かコメント入れてもらってもいいでしょうか? 本当にご丁寧な対応、ありがとうございました!
hoshi-takanori

2021/02/08 09:29

自分としてはもう少しお伝えしたいことがあるのですが、書くのが遅くてすみません。 内容としては、 ・redux store のデータ構造および責務の分担 ・redux をもっと楽に書く方法 (redux toolkit) を考えていますので、もう少々お待ちください。
yuki-o0413

2021/02/08 09:30

いいですか!? はい!hoshi様にもっと教えていただきたいと思っていたんです。 ご提案いただいてありがたい限りです! お待ちしています。ぜひ宜しくお願いします!!
hoshi-takanori

2021/02/09 06:48

redux store のデータ構造および責務の分担、例えばこんな感じですかね。 https://github.com/hoshi-takanori/ReactTypeLsn/compare/fork-10...fork-11 以前は、IdState と NameState に分かれてましたが、そもそもこれらを分ける必要があるのだろうか? というのが今回の修正のポイントです。関連する情報は一つにまとめた方が取り扱いやすいので、ItemState という方を定義しました。 さらに、バリデーションも reducer で行うことにして、state の中に含めました。また、Save ボタンを押したときの処理もアクションにして、reducer で行うようにしてみました。この辺はいろんなやり方が考えられますが、責務の分担ということで、本来 React のコンポーネントは「表示部品」であり、これらがバリデーションやボタンを押されたときのロジックを持つのはおかしい、という考え方になります。 コードの変更量は多いですが、たぶんもう解説なしで読めると思うので、読んでみてください。
yuki-o0413

2021/02/11 05:41

時間がかかってしまってごめんなさい! hoshi様に直していただいた部分を修正し、プッシュを行いました。 Reduxで関数も管理下に置くことができたんですね(当たり前のことなんでしょうけれど、思いついていなかったです)! reducerでスイッチ文使うやり方も、独学じゃ絶対に辿り着けなかったやり方です! それと、今更ですが、useSelector とuseDispatchの書き方、面白いですね。何度も書かないと、慣れるまで時間かかるかもしれないですが、積極的に使いたいと思う書き方だと思いました。 どれくらい修行を積んだら、自分でこんな綺麗なコード書けるようになるんだろう・・・!(><) https://github.com/yuki-o0413/ReactTypeLsn/tree/master/src
hoshi-takanori

2021/02/13 08:58

最後は redux toolkit ですね。 https://github.com/hoshi-takanori/ReactTypeLsn/compare/fork-12...fork-13 修正内容はほぼこちらの記事の通りなので、詳しくはこちらをお読みください。 https://future-architect.github.io/articles/20200501/ actions.ts が不要になり、createStores.ts もだいぶスリムになりました。 こういう柔軟なことができるのが JavaScript で、それにしっかりと型がつくのが TypeScript のすごいところだと思います。 という感じで、ひとまずここで区切りをつけたいと思います。お疲れさまでした。 また何かあったらここに書き込むか、相互フォローにしたので回答依頼をいただければと思います。
yuki-o0413

2021/02/13 09:04

なんとお礼を言ったら良いのか!! 本当にありがとうございました! redux toolkitって初めて聞くものですが、教えていただいたもの、取り組んでみます! はい、よかったら、是非また相談に乗っていただきたいです。助かります????
guest

回答1

0

ベストアンサー

詳しくは「質問への追記・修正の依頼」欄をどうぞ。

投稿2021/02/13 09:00

hoshi-takanori

総合スコア7901

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

yuki-o0413

2021/02/13 09:07

環境構築から、教えていただき、TypeScript、Reduxの知識をご教授いただきました。 ここまで自分一人ではここまでたどり着けませんでした! 貴重なお時間をいただき本当にありがとうございました!!
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.35%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問