LoginSignup
0
0

More than 1 year has passed since last update.

react トラゼミ入門実践編 メモ

Posted at

環境構築

作業フォルダにて

$ npx create-react-app chatbot-demo
$ cd chatbot-demo
$ code .  #chatbot-demoをvscodeを開いている

vscode上にて

$ npm start

material-uiをインストールする(ReactのUIフレームワーク)

npm install --save @material-ui/core @material-ui/icons @material-ui/system

インストール後以下が追加される

packege.json
{
  "name": "chatbot-demo",
  "version": "0.1.0",
  "private": true,
  "dependencies": {
    "@material-ui/core": "^4.11.3",    //追加
    "@material-ui/icons": "^4.11.2",   //追加
    "@material-ui/system": "^4.11.3",  //追加
    "@testing-library/jest-dom": "^5.11.9",
    "@testing-library/react": "^11.2.5",
    "@testing-library/user-event": "^12.8.3",
    "react": "^17.0.2",
    "react-dom": "^17.0.2",
    "react-scripts": "4.0.3",
    "web-vitals": "^1.1.1"
  },

Robotoフォントと、フォントアイコンを設定

public/index.html
.
.
-->
<!-- 追加 -->
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap" />
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons" />
<!-- 追加 -->
<title>React App</title>
.
.

html:public/index.htmlのコメントアウトしている部分は必要ないため消す。

Firebase

Firebaseはgoogleが提供するアプリのプラットフォーム
下記に移動

url
https://console.firebase.google.com/

①プロジェクト名を記載し、国を選択し作成
②左上の歯車を押し、GCPにasia-northeast1を選択
③マイアプリの</>を押し、アプリ名を記載しfairebase Hostingを設定する
④Cloud Firestoreに移動し使用を始める

その後、作業フォルダのターミナルにて下記実行

$ npm install -g firebase-tools 
(上記はPCの環境全体にインストールするため、一度でも行っていれば無視)
$ npm install --save firebase

下記が追加されているのを確認

packege.json
"firebase": "^8.3.1" 

作業フォルダのターミナルにて下記実行

$ firebase login
(yes,noはエンターキーで進み、許可をブラウザ上で求められるため、許可する)
$ firebase init

下記選択(スペースキーで選択)
Firestore: Deploy rules and create indexes for Firestore
Functions: Configure and deploy Cloud Functions
Hosting: Configure and deploy Firebase Hosting sites

Use an existing projectを選択

プロジェクト名を選択

What file should be used for Firestore Rules? enterを押す

What file should be used for Firestore indexes? enterを押す

What language would you like to use to write Cloud Functions? 記載したい方 typeにした

Do you want to use ESLint to catch probable bugs and enforce style? enterを押す

Do you want to install dependencies with npm now? enterを押す

What do you want to use as your public directory? buildと記載

Configure as a single-page app (rewrite all urls to /index.html)? Yを押す

Set up automatic builds and deploys with GitHub?  enterを押す

下記に変更

firestore.rules
allow read;
allow write: if request.auth.uid != null;
$ npm run build

1行目をコメントアウトする

build/functions/src/index.ts
// import * as functions from "firebase-functions"; コメントアウトする

// // Start writing Firebase Functions
// // https://firebase.google.com/docs/functions/typescript
.
.

package.jsonの「"react-app"」の部分だけ削除します。

package.json
  "eslintConfig": {
    "extends": [
      "react-app"  ←ここだけ
      "react-app/jest"
    ]
  }
firebase.json
 "functions": {
    "predeploy": [
      "npm --prefix \"$RESOURCE_DIR\" run lint", 削除
      "npm --prefix \"$RESOURCE_DIR\" run build" 削除
    ]
  },
$ cd functions
$ npm run build

もとの作業フォルダに戻って

$ firebase deploy    

stateとモデルの設計

App.js⇒App.jsxにする
App.jsxをクラスコンポーネントにして、中身をdivタグのみにする
(ロゴのインポートも削除)

src/App.jsx
function App() {
  return (
.
.
  );
}

export default App;



export default class App extends React.Component {
  render() {
    return (
      <div>
      </div>
    );
  }
}

stateを追記

src/App.jsx
export default class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      answers: [],
      chats: [],
      currentID: "init",
      dataset: {},
      open: false
    }
  }

srcフォルダにdataset.jsを作成し、以下要領でデータを作成する。

src/dataset.js
const defaultDataset = {
    "init": {
        answers: [
            {content: "仕事を依頼したい", nextId: "job_offer"},
            {content: "エンジニアのキャリアについて相談したい", nextId: "consultant"},
            {content: "学習コミュニティについて知りたい", nextId: "community"},
            {content: "お付き合いしたい", nextId: "dating"},
        ],
        question: "こんにちは!🐯トラハックへのご用件はなんでしょうか?",
    },
.
.
export default defaultDataset

App.jsで上記をインポートし、stateのdatasetの中に入れる

src/App.jsx
import React from 'react';
import './App.css';
import defaultDataset from './dataset'; //追記
.
.

    super(props);
      .
      .
      dataset: defaultDataset, //変更
      .
      .

src/assets/stylesフォルダを作成し、index.cssを移動、style.cssを作成

src/index.js
import './assets/styles/index.css'; //変更
src/App.jsx
import './App.css'; //削除
import './assets/styles/style.css'; //追加

returnに以下を記載

src/App.jsx
    return (
      <div>
        <section className="c-section">
          <div className="c-box">
            {this.state.currentID}
          </div>
        </section>
      </div>
    );

関数コンポーネント

src/App.jsx
    return (
      <div>
        <section className="c-section">
          <div className="c-box">
            <AnswersList /> //追記
          </div>
        </section>
      </div>
    );

AnswersList.jsxをsrc/conponents内に追加

src/conponents/AnswersList.jsx
import React from 'react'

const AnswersList = () => {
    return(
        <></>
    )
}

export default AnswersList

エントリーポイントを作成したいため、src/conponents内にindex.jsを追加

src/conponents/index.js
export {default as AnswersList} from './AnswersList'
src/App.jsx
import {AnswersList} from "./components/index"

Answer.jsxを追加

src/conponents/AnswersList.jsx
import {Answer} from './index' //追加
.
.

  return(
    <div className="c-grid__answer">
      <Answer />
      <Answer />
      <Answer />
      <Answer />
    </div>
  )
src/conponents/Answer.jsx
import React from 'react';

const Answer = () => {}

export default Answer
src/conponents/index.js
export {default as AnswersList} from './AnswersList'
export {default as Answer} from './Answer' //追加

material ui buttonで検索し、ボタンを追加する

src/conponents/Answer.jsx
import React from 'react';
import { makeStyles } from '@material-ui/core/styles'; 
import Button from '@material-ui/core/Button'; 

const useStyles = makeStyles((theme) => ({
  root: {
  },
}));

const Answer = () => {
  // const classes = useStyles();

  return(
    <Button variant="contained">Default</Button>
  )
}

export default Answer

ボタン内の文字をpropsで受け取る

src/conponents/Answer.jsx
const Answer = (props) => {
  // const classes = useStyles();

  return(
    <Button variant="contained">
      {props.content}
    </Button>
  )
}
src/conponents/AnswersList.jsx
    <div className="c-grid__answer">
      <Answer content={"hoge"}/>
      <Answer content={"huga"} />
      <Answer content={"foo"} />
      <Answer content={"bar"} />
    </div>

実際のdatasetを送る

src/conponents/AnswersList.jsx
const AnswersList = (props) => { //propsを追記
src/App.jsx
initAnswer = () => {
    const initDataset = this.state.dataset[this.state.currentID];
    const initAnswers = initDataset.answers;

    this.setState({
      answers: initAnswers
    })
  }
  componentDidMount() {
    this.initAnswer()
  }

  render() {
           //.
          <div className="c-box">
            <AnswersList answers={this.state.answers}/>
          </div>
           //.
src/conponents/AnswersList.jsx
const AnswersList = (props) => {
  return(
    <div className="c-grid__answer">
      {props.answers.map((value,index) => {
        return <Answer content={value.content} key={index.toString()} />
      })}
    </div>
  )
}

image.png

条件分岐、コールバック関数のバインド

Chats.jsx,Chat.jsxをsrc/conponents内に追加

src/conponents/Chats.jsx
import React from 'react'

const Chats = () => {

}

export default Chats
src/conponents/Chat.jsx
import React from 'react'

const Chat = () => {

}

export default Chat
src/conponents/index.js
export {default as Chats} from './Chats'
export {default as Chat} from './Chat'
src/App.jsx
import {AnswersList, Chats} from "./components/index" //追加

<div className="c-box">
  <Chats /> //追加
  <AnswersList answers={this.state.answers} />
</div>

chats.jsxにmaterial ui listのAlign list itemsを適応する

src/conponents/Chats.jsx
import React from 'react'
import { makeStyles } from '@material-ui/core/styles';
import List from '@material-ui/core/List';
import ListItem from '@material-ui/core/ListItem';
import Divider from '@material-ui/core/Divider';
import ListItemText from '@material-ui/core/ListItemText';
import ListItemAvatar from '@material-ui/core/ListItemAvatar';
import Avatar from '@material-ui/core/Avatar';
import Typography from '@material-ui/core/Typography';

const useStyles = makeStyles((theme) => ({
  root: {
    width: '100%',
    maxWidth: '36ch',
    backgroundColor: theme.palette.background.paper,
  },
  inline: {
    display: 'inline',
  },
}));

const Chats = () => {
  const classes = useStyles();
  return (
    <List className={classes.root}>
      <ListItem alignItems="flex-start">
        <ListItemAvatar>
          <Avatar alt="Remy Sharp" src="/static/images/avatar/1.jpg" />
        </ListItemAvatar>
        <ListItemText
          primary="Brunch this weekend?"
          secondary={
            <React.Fragment>
              <Typography
                component="span"
                variant="body2"
                className={classes.inline}
                color="textPrimary"
              >
                Ali Connors
              </Typography>
              {" — I'll be in your neighborhood doing errands this…"}
            </React.Fragment>
          }
        />
      </ListItem>
    </List>
  )

}

export default Chats

listitemはchat.jsxで使用するため、いらないものを削除しながら分ける

src/conponents/Chats.jsx
import React from 'react'
import { makeStyles } from '@material-ui/core/styles';
import List from '@material-ui/core/List';
import {Chat} from "./index";

const useStyles = makeStyles((theme) => ({
  root: {
    width: '100%',
    maxWidth: '36ch',
    backgroundColor: theme.palette.background.paper,
  },
}));

const Chats = () => {
  const classes = useStyles();
  return (
    <List className={classes.root}>
      <Chat />
      <Chat />
      <Chat />
    </List>
  )
}

export default Chats
src/conponents/Chat.jsx
import React from 'react'
import ListItem from '@material-ui/core/ListItem';
import ListItemAvatar from '@material-ui/core/ListItemAvatar';
import Avatar from '@material-ui/core/Avatar';

const Chat = () => {
  return ( 
    <ListItem>
      <ListItemAvatar>
        <Avatar alt="icon" src="/static/images/avatar/1.jpg" />
      </ListItemAvatar>
      <div className="p-chat__bubble">ダミーテキスト</div>
    </ListItem>
  )
}

export default Chat

chatsに初期状態を渡す

App.jsx

  initChats = () => {
    const initDataset = this.state.dataset[this.state.currentID];
    const chat = {
      text: initDataset.question,
      type: 'question'
    }

    const chats = this.state.chats
    chats.push(chat)

    this.setState({
      chats: chats
    })
  }

  componentDidMount() {
    this.initChats();
    this.initAnswer();
  }
src/conponents/Chats.jsx
const Chats = (props) => {
  const classes = useStyles();
  return (
    <List className={classes.root}>
      {props.chats.map((chat, index) => {
        return <Chat text={chat.text} type={chat.type} key={index.toString()} />
      })}
    </List>
  )
}

chatで受け取る

src/conponents/Chat.jsx
const Chat = (props) => {
  const isQuestion = (props.type === 'question');
  const classes = isQuestion ? 'p-chat__row' : 'p-chat__reverse'
  return ( 
    <ListItem className={classes}>
      <ListItemAvatar>
        <Avatar alt="icon" src="/static/images/avatar/1.jpg" />
      </ListItemAvatar>
      <div className="p-chat__bubble">{props.text}</div>
    </ListItem>
  )
}

return内の条件分岐の書き方
ifのみ

{(list.length === 0) && (
  <Loading/>
)}

if else

{ifQuestion ? (
  <Avatar src={Torahack} />
) : (
  <Avatar src={NoProfile} />
)}

上記を用いて、chat画像を変更する

src/conponents/Chat.jsx
      <ListItemAvatar>
        {isQuestion ? (
          <Avatar alt="icon" src={Torahack} />
        ) : (
          <Avatar alt="icon" src={NoProfile} />
        )}

      </ListItemAvatar>

対話を実装する
コールバック関数にはbind()を用いる
(renderの度に、新しく関数が生成されるのを阻止するため)

App.jsx
export default class App extends React.Component {
  constructor(props) {
    .
    .
    }
    this.selectAnswer = this.selectAnswer.bind(this) //bind()処理
  }
  .
  .
  displayNextQuestion = (nextQuestionId) => {
    const chats = this.state.chats
    chats.push({
      text: this.state.dataset[nextQuestionId].question,
      type: 'question'
    })

    this.setState({
      answers: this.state.dataset[nextQuestionId].answers,
      chats: chats,
      currentID: nextQuestionId
    })
  }


  selectAnswer = (selectedAnswer, nextQuestionId) => {
    switch(true) {
      case (nextQuestionId === 'init'):
        this.displayNextQuestion(nextQuestionId)
        break;
      default:
        const chats = this.state.chats
        chats.push({
          text: selectedAnswer,
          type: 'answer'
        })

        this.setState({
          chats: chats
        })

        this.displayNextQuestion(nextQuestionId)
        break;
    }
  }
  componentDidMount() {
    const initAnswer = ""
    this.selectAnswer(initAnswer, this.state.currentID)
  }
.
.
          <div className="c-box">
            <Chats chats={this.state.chats}/>
            <AnswersList answers={this.state.answers} select={this.selectAnswer} />
          </div>
src/conponents/AnswersList.jsx
        return <Answer content={value.content} nextId={value.nextId} key={index.toString()} select={props.select} />
src/conponents/Answer.jsx
  return(
    <Button variant="contained" color="primary" onClick={() => props.select(props.content, props.nextId)}>
      {props.content}
    </Button>
  )

Material-UIのカスタマイズ

Hook APIの使い方
①インストール

$ npm install --save @material-ui/style

②import

import {createStyles, makeStyles} from "@material-ui/core/styles";

③関数を定義

const useStyles = makeStyles(() =>
  createStyles({
    "root": {
      padding: 0
    }
  }),
);

記述ルール
①json形式
②キャメルケース(javascriptはハイフンを使えないため)
③数値はそのまま
④クォーテーションで文字列を囲む

適用方法
①コンポーネント内で宣言
②オブジェクト型として使える

const Answer = (props) => {
  const classes = useStyles();
  return(
    <Button className={classes.button}>
      {props.answer.content}
    <Button>
  );
};

ライフサイクルの活用

3種類のライフサイクル
Mounting:コンポーネントが配置される瞬間
constructor()⇒render()⇒componentDidMount()
Updating:コンポーネントが変更される瞬間
render()⇒componentDidUpdate()
Unmounting:コンポーネントが破棄される瞬間
componentWillUnmount()

自動スクロールの実装
idの付与

src/conponents/Chats.jsx
<List className={classes.chats} id={"scroll-area"}>

componentDidUpdateを記載

App.jsx
  componentDidUpdate(prevProps, prevState, snapshot) {
    const scrollArea = document.getElementById('scroll-area')
    if (scrollArea) {
      scrollArea.scrollTop = scrollArea.scrollHeight
    }
  }

setTimeout()で遅延表示

App.jsx
selectAnswer = (selectedAnswer, nextQuestionId) => {
    switch(true) {
      case (nextQuestionId === 'init'):
        setTimeout(() => this.displayNextQuestion(nextQuestionId),500)
        break;
      default:
        const chats = this.state.chats
        chats.push({
          text: selectedAnswer,
          type: 'answer'
        })

        this.setState({
          chats: chats
        })

        setTimeout(() => this.displayNextQuestion(nextQuestionId), 1000)
        break;
    }
  }

別urlに飛ばす

App.jsx
      case (/^https:*/.test(nextQuestionId)):
        const a = document.createElement('a');
        a.href = nextQuestionId;
        a.target = '_brank';
        a.click();
        break

問い合わせ用モーダル

components/Forms/formDialog.jsxを作成
Material-UIのAlertsを一部用いて作成する。

jsx/App.jsx
.
.
export default class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
    .
    .
    }
    this.selectAnswer = this.selectAnswer.bind(this)
    this.handleClose = this.handleClose.bind(this)
    this.handleClickOpen = this.handleClickOpen.bind(this)
  }
.
.
  handleClickOpen = () => {
    this.setState({ open: true });
  };

  handleClose = () => {
    this.setState({ open: false });
  };
.
.
          <div className="c-box">
            <Chats chats={this.state.chats}/>
            <AnswersList answers={this.state.answers} select={this.selectAnswer} />
            <FormDialog open={this.state.open} handleClose={this.handleClose} />
          </div>
.
.
components/Forms/formDialog.jsx
import React from 'react'
import Button from '@material-ui/core/Button';
import Dialog from '@material-ui/core/Dialog';
import DialogActions from '@material-ui/core/DialogActions';
import DialogContent from '@material-ui/core/DialogContent';
import DialogContentText from '@material-ui/core/DialogContentText';
import DialogTitle from '@material-ui/core/DialogTitle';

export default class FormDialog extends React.Component {
  constructor(props) {
    super(props);
    }



  render() {
    return(
      <Dialog
        open={this.props.open}
        onClose={this.props.handleClose}
        aria-labelledby="alert-dialog-title"
        aria-describedby="alert-dialog-description"
      >
        <DialogTitle id="alert-dialog-title">{"Use Google's location service?"}</DialogTitle>
        <DialogContent>
          <DialogContentText id="alert-dialog-description">
            Let Google help apps determine location. This means sending anonymous location data to
            Google, even when no apps are running.
          </DialogContentText>
        </DialogContent>
        <DialogActions>
          <Button onClick={this.props.handleClose} color="primary">
            Disagree
          </Button>
          <Button onClick={this.props.handleClose} color="primary" autoFocus>
            Agree
          </Button>
        </DialogActions>
      </Dialog>
    )
  }
}

お問い合わせしたいを選択した際に、モーダルが表示されるようにする。

App.jsx
case (nextQuestionId === 'contact'):
        this.handleClickOpen();
        break;

TextInput.jsx作成(Material-UIのTextfiledを使用)

components/Forms/TextInput.jsx
import React from 'react';
import TextField from '@material-ui/core/TextField';

const TextInput = (props) => {
  return(
    <TextField
      fullWidth={true}
      label={props.label}
      margin={"dense"}
      multiline={props.multiline}
      rows={props.rows}
      value={props.value}
      type={props.type}
      onChange={props.onChange}
    />
  )
}

export default TextInput
components/Forms/FormDialog.jsx
import React from 'react'
import Button from '@material-ui/core/Button';
import Dialog from '@material-ui/core/Dialog';
import DialogActions from '@material-ui/core/DialogActions';
import DialogContent from '@material-ui/core/DialogContent';
import DialogTitle from '@material-ui/core/DialogTitle';
import TextInput from './TextInput'

export default class FormDialog extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      name: "",
      email: "",
      description: ""
    }
    this.inputName = this.inputName.bind(this)
    this.inputEmail = this.inputEmail.bind(this)
    this.inputDescription = this.inputDescription.bind(this)
  }

  inputName = (event) => {
    this.setState({ name: event.target.value})
  }
  inputEmail = (event) => {
    this.setState({ email: event.target.value})
  }
  inputDescription = (event) => {
    this.setState({ description: event.target.value})
  }



  render() {
    return(
      <Dialog
        open={this.props.open}
        onClose={this.props.handleClose}
        aria-labelledby="alert-dialog-title"
        aria-describedby="alert-dialog-description"
      >
        <DialogTitle id="alert-dialog-title">お問い合わせ用フォーム</DialogTitle>
        <DialogContent>
          <TextInput
            label={"お名前(必須)"} multiline={false} rows={1} value={this.state.name} type={"text"} onChange={this.inputName}
          />
          <TextInput
            label={"メールアドレス(必須)"} multiline={false} rows={1} value={this.state.email} type={"email"} onChange={this.inputEmail}
          />
          <TextInput
            label={"お問い合わせ内容(必須)"} multiline={true} rows={5} value={this.state.description} type={"text"} onChange={this.inputDescription}
          />
        </DialogContent>
        <DialogActions>
          <Button onClick={this.props.handleClose} color="primary">
            Disagree
          </Button>
          <Button onClick={this.props.handleClose} color="primary" autoFocus>
            Agree
          </Button>
        </DialogActions>
      </Dialog>
    )
  }
}

slackへ問い合わせ内容を通知する
Incoming Webhook
https://test-fwz7429.slack.com/apps/new/A0F7XDUAZ--incoming-webhook-

components/Forms/FormDialog.jsx
  submitForm = () => {
    const name = this.state.name
    const email = this.state.email
    const description = this.state.description

    const payload = {
      text: "お問い合わせがありました\n" +
            "お名前:" + name + "\n" +
            "Email:" + email + "\n" +
            "問い合わせ内容:\n" + description
    }

    const url = "Incoming Webhookで出てきたurl"

    fetch(url, {
      method: 'POST',
      body: JSON.stringify(payload)
    }).then(() => {
      alert('送信が完了しました。追ってご連絡致します。')
      this.setState({
        name: "",
        email: "",
        description: ""
      })
      return this.props.handleClose()
    })
  }
.
.
          <Button onClick={this.submitForm} color="primary" autoFocus>
            送信する
          </Button>

Firebase Firestore(データベース)の利用

REST APIを作る
①dataset.jsをjsonファイルに変える
最後の,は使えないなど注意

②Cloud Functions でhttps関数の作成

functions.src.index.ts
import * as functions from 'firebase-functions';
import * as admin from "firebase-admin";
admin.initializeApp(); //adminの初期化
const db = admin.firestore(); //admin権限でfirestoreを操作するもの

//APIを叩くとレスポンスがかえってくるため、それを作成する
const sendResponse = (response: functions.Response, statusCode: number, body: any) => {
  response.send({
    statusCode,
    body: JSON.stringify(body)
  })
}

export const addDataset = functions.https.onRequest(async (req: any, res: any) => {
  if (req.method !== 'POST') { //POSTじゃないとエラーを返したい
    sendResponse(res, 405, {error: 'Invalid Request!'})
  } else {
    const dataset = req.body //渡すデータを定数化(dataset.json)
    for (const key of Object.keys(dataset)) { //rubyのeachメソッドっぽいことしてる(Object.keys(dataset).each do |key|)
      const data = dataset[key] //keyに紐づくvalueを取得
      await db.collection('questions').doc(key).set(data) //questionsフォルダのKeyドキュメントにdataが入っている
    }
    sendResponse(res, 200, {message: "successfully added dataset!"})
  }
})

③デプロイ

$ cd functions
$ npm run build
$ cd ../
$ firebase deploy     

④curlコマンドでAPIを叩く
-X⇒メソッド指定 -H⇒データ形式指定 -d⇒渡すデータ指定(@をつけるとローカル)
Cloud FunctionsのURLを指定

$cd src
$curl -X POST -H "Content-Type: application/json" -d @dataset.json {firebaseのurl}

Firestoreを使う準備
src/firebase/config.jsを作成
firebaseの設定(プロジェクトを設定)をクリック
Firebase SDK snippet 構成を選択
コードをコピーして貼り付け

src/firebase/config.js
// For Firebase JS SDK v7.20.0 and later, measurementId is optional
const firebaseConfig = {
.
.

};

export default firebaseConfig

src/firebase/index.jsを作成

src/firebase/index.js
import firebase from 'firebase/app'
import 'firebase/firestore'
import firebaseConfig from "./config"

firebase.initializeApp(firebaseConfig)
export const db = firebase.firestore()
app.jsx
import {db} from './firebase/index'
app.jsx
  componentDidMount() {
    (async() => {
      const dataset = this.state.dataset;

      await db.collection('questions').get().then(snapshots => {
        snapshots.forEach(doc => {
          const id = doc.id;
          const data = doc.data();
          dataset[id] = data;
        })
      })

      this.initDataset(dataset);
      const initAnswer = "";
      this.selectAnswer(initAnswer, this.state.currentID);
    })()

React Hooks

useState()の使いかた 関数コンポーネントでstateを扱えるようにするもの
import React, {useState} from 'react';
const [state変数名, state変更関数名] = useState(state初期値);
<input onclick={() => state変更関数名(値)

useEffectの使い方

useEffect(() => {
  Renderごとに呼ばれる処理
  return () => {
    アンマウント時に呼ばれる処理
  }
})

useEffect(() => { return() => {} } )

第二引数を与えれば、前回レンダーと今回レンダーで比較し
変更がある場合に、Callback関数を実行
※空の配列を渡すと、最初の1回のみ実行(componentDidMount)

useEffect(() => {
  Renderごとに呼ばれる処理
}, [])

useEffect(() => {}, [])

useCallback()を使うメリット

通常時:コンポーネント内で宣言したコールバック関数は、renderごとに生成される
使用時:コールバック関数の再生成を抑止=不変値化
   (bind()と似た役割)
子コンポーネントにprops関数を渡す場合に使う

useCallback(() => {},[再描画の条件])
  const addChats = (chat) => {
    setChats(prevChats => {
      return [...prevChats, chat]
    })
  }
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0