Sail版
Dockerサービス起動とDockerコンテナ起動cd /mnt/c/Users/user/git/react_demo2/dev sudo service docker start ./vendor/bin/sail up -dビルド/コンパイル
./vendor/bin/sail npm run buildDockerコンテナ停止とDockerサービス終了
./vendor/bin/sail stop sudo service docker stop
npm install react-router-dom @types/react-router-dom
resources/ts/index.tsx
import React from 'react';
import ReactDOM from 'react-dom';
import {
BrowserRouter as Router,
Route,
Routes
} from 'react-router-dom';
import Demo from './demo';
const App: React.FC = () => {
return (
<Router>
<Routes>
<Route path="/demo" element={<Demo />} />
{/* 他のルートもこちらに追加可能 */}
</Routes>
</Router>
);
};
export default App;
ReactDOM.render(<App />, document.getElementById('react_app'));
resources/ts/demo.tsx
import React from 'react';
const Demo: React.FC = () => {
return <div>Demo Page!</div>;
};
export default Demo;
resources/views/demo/index.blade.php
<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Laravel</title>
@viteReactRefresh
@vite(['resources/sass/app.scss', 'resources/ts/index.tsx'])
</head>
<body>
<div id="react_app"></div><!-- Reactの埋め込み部分 -->
</body>
</html>
resources/ts/cmn/ToggleButton.tsx
// ToggleButton.tsx
import React, { useState } from 'react';
type ToggleButtonProps = {
targetId: string;
label: string;
};
const ToggleButton: React.FC<ToggleButtonProps> = ({ targetId, label }) => {
const [isVisible, setIsVisible] = useState<boolean>(false);
const toggleVisibility = () => {
const element = document.getElementById(targetId);
if (element) {
if (isVisible) {
element.style.display = 'none';
} else {
element.style.display = 'block';
}
setIsVisible(!isVisible);
}
};
return <button onClick={toggleVisibility} class="btn btn-info">{isVisible ? `${label}(閉じる)` : `${label}`}</button>;
};
export default ToggleButton;
resources/ts/demo.tsx(クライアント側へToggleButton関数コンポーネントを実装)
import React from 'react';
import ReactDOM from 'react-dom';
import ToggleButton from './cmn/ToggleButton';
const Demo: React.FC = () => {
return <div>Demo Page!<button type="button" class='btn btn-success'>テスト</button></div>;
};
ReactDOM.render(<ToggleButton targetId="cat_contents" label="コンテンツ1" />, document.getElementById('toggle-button'));
export default Demo;
resources/views/demo/index.blade.php
<div id="toggle-button"></div> <!-- ボタンをこの要素に埋め込みます -->
<div id="cat_contents" style="display:none;">
秋の初めごろはバッタやコオロギなどの虫たちが活発に活動します。<br>
キュウリの苗のほとんどは、それらの虫たちに食べつくされてしまいます。<br>
しかし、雑草の側で育つキュウリは、虫の被害が半分で済みます。<br>
#自然 #農業
</div>
resources/ts/demo/ItemList.tsx
import React from 'react';
interface Item {
id: number;
name: string;
}
const ItemList: React.FC = () => {
const items: Item[] = [
{ id: 1, name: "アイテム1" },
{ id: 2, name: "アイテム2" },
{ id: 3, name: "アイテム3" },
];
return (
<div>
<h1>アイテム一覧の見本</h1>
<ul>
{items.map((item) => (
<li key={item.id}>{item.name}</li>
))}
</ul>
</div>
);
};
export default ItemList;
resources/ts/demo.tsx
import React from 'react';
import ReactDOM from 'react-dom';
import ItemList from './demo/ItemList';
ReactDOM.render(<ItemList />, document.getElementById('item_list'));
export default Demo;
resources/views/demo/index.blade.php
<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Laravel</title>
@viteReactRefresh
@vite(['resources/sass/app.scss', 'resources/ts/index.tsx'])
</head>
<body>
<div id="item_list"></div>
</body>
</html>
resources/ts/demo/itemListAdd.tsx
import React, { useState } from 'react';
interface Item {
id: number;
name: string;
}
const ItemListAdd: React.FC = () => {
const [inputValue, setInputValue] = useState<string>('');
const [items, setItems] = useState<Item[]>([]);
const handleInputChange = (event: React.ChangeEvent<HTMLInputElement>) => {
setInputValue(event.target.value);
};
const handleAddItem = () => {
if (inputValue.trim() === '') return;
const newItem: Item = {
id: new Date().getTime(),
name: inputValue,
};
setItems([...items, newItem]);
setInputValue('');
};
return (
<div>
<h1>アイテム一覧・追加</h1>
<input
type="text"
placeholder="アイテム名を入力してください"
value={inputValue}
onChange={handleInputChange}
/>
<button onClick={handleAddItem}>追加</button>
<ul>
{items.map((item) => (
<li key={item.id}>{item.name}</li>
))}
</ul>
</div>
);
};
export default ItemListAdd;
resources/ts/demo.tsx
import React from 'react';
import ReactDOM from 'react-dom';
import ItemListAdd from './demo/ItemListAdd';
ReactDOM.render(<ItemListAdd />, document.getElementById('item_list_add'));
export default Demo;
resources/views/demo/index.blade.php
<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Laravel</title>
@viteReactRefresh
@vite(['resources/sass/app.scss', 'resources/ts/index.tsx'])
</head>
<body>
<div id="item_list_add" class="mt-3"></div>
</body>
</html>
useEffect とは
useEffect は React Hooks の一部として React 16.8 で導入されました。 クラスコンポーネントのライフサイクルメソッド(例:componentDidMount, componentDidUpdate, componentWillUnmount)に相当する機能を、関数コンポーネント内で使えるように提供するものです。コンポーネントのマウント時に一度だけ実行:
useEffect(() => {
console.log("Component did mount!");
}, []);
特定の変数の変更時に実行:
const [count, setCount] = useState(0);
useEffect(() => {
console.log("Count has changed!", count);
}, [count]);
非同期処理のクリーンアップ時に実行:
useEffect(() => {
const timerId = setInterval(() => {
console.log("This runs every second!");
}, 1000);
return () => {
clearInterval(timerId); // クリーンアップ関数
};
}, []);
注意点useEffectの詳細解説
useEffect(() => {
// 何らかの副作用を起こすコード
}, [依存配列]);
副作用の関数: useEffect の第1引数として渡される関数の中には、副作用を伴うコードを書きます。これにはデータの取得、DOM の更新、タイマーの設定などが含まれます。resources/ts/demo/GetJsonTest.tsx
import React, { useEffect, useState } from 'react';
type HiddenData = {
key: string;
// 他の必要な型情報も追加できます
};
const GetJsonTest: React.FC = () => {
const [data, setData] = useState<HiddenData | null>(null);
useEffect(() => {
const rawData = document.getElementById("data_json") as HTMLInputElement;
if (rawData) {
const parsedData: HiddenData = JSON.parse(rawData.value);
setData(parsedData);
}
}, []); // このeffectはコンポーネントのマウント時に一度だけ実行されます
if (!data) return null; // データがまだロードされていないか、データが存在しない場合のハンドリング
return (
<div>
<h1>{data.key}</h1>
{/* 他のデータの表示も行えます */}
</div>
);
};
export default GetJsonTest;
resources/ts/demo.tsx
import React from 'react';
import ReactDOM from 'react-dom';
import GetJsonTest from './demo/GetJsonTest';
ReactDOM.render(<GetJsonTest />, document.getElementById('get_json_test'));
export default Demo;
resources/views/demo/index.blade.php
<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Laravel</title>
@viteReactRefresh
@vite(['resources/sass/app.scss', 'resources/ts/index.tsx'])
</head>
<body>
<input type="hidden" id="data_json" value='{"key": "猫にエサを与えなかったが、イヌがエサを猫に与えた。"}' />
<div id="get_json_test"></div>
</body>
</html>
フロントエンド:resources/ts/demo/SpaDemo.tsx
import React, { useState, useEffect } from 'react';
import axios from '../cmn/Spa';
const SpaDemo: React.FC = () => {
const [data, setData] = useState<any>(null);
const [errorHtml, setErrorHtml] = useState<string | null>(null);
useEffect(() => {
const fetchData = async () => {
try {
let postData = {'buta':'豚の高級住宅'};
const response = await axios.post('demo/spa_demo',postData);
if(typeof response.data === 'string'){
setErrorHtml(response.request.responseText);
}
setData(response.data);
} catch (error) {
console.error('Error fetching data:', error);
}
};
fetchData();
}, []);
if (!data) return <p>Loading...</p>;
return (
<div>
<h1>SPA Demo</h1>
{errorHtml && <div dangerouslySetInnerHTML={{ __html: errorHtml }} />}
<pre>{JSON.stringify(data, null, 2)}</pre>
</div>
);
}
export default SpaDemo;
フロントエンド:resources/ts/cmn/Spa.ts
import axios from 'axios';
let baseURL = `${window.location.protocol}//${window.location.hostname}`;
if (window.location.port) {
baseURL += `:${window.location.port}`;
}
const instance = axios.create({
baseURL: baseURL,
timeout: 10000,
headers: {'X-Requested-With': 'XMLHttpRequest'},
});
const token = document.querySelector('meta[name="csrf-token"]')?.getAttribute('content');
if (token) {
instance.defaults.headers.common['X-CSRF-TOKEN'] = token;
} else {
console.error('CSRF token not found');
}
export default instance;
バックエンド:app/Http/Controllers/DemoController.php
class DemoController extends Controller
{
public function spa_demo(Request $request){
// // すでにログアウトになったらlogoutであることをフロントエンド側に知らせる。
// if(¥Auth::id() == null) return json_encode(['err_msg'=>'logout']);
$buta = $request->buta;
$res = [];
$res['buta'] = $buta;
$res['name'] = '新しい猫2';
$res['age'] = 1;
$res['date'] = '2020-7-23';
$json = json_encode($res, JSON_HEX_TAG | JSON_HEX_QUOT | JSON_HEX_AMP | JSON_HEX_APOS);
return $json;
}
}
バックエンド:resources/views/demo/index.blade.php
<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="csrf-token" content="{{ csrf_token() }}">
<title>Laravel</title>
@viteReactRefresh
@vite(['resources/sass/app.scss', 'resources/ts/index.tsx'])
</head>
<body>
<div id="spa_demo" class="mt-3"></div>
</body>
</html>
バックエンド:routes/web.php
<?php
use Illuminate¥Support¥Facades¥Route;
/*
|--------------------------------------------------------------------------
| Web Routes
|--------------------------------------------------------------------------
|
| Here is where you can register web routes for your application. These
| routes are loaded by the RouteServiceProvider and all of them will
| be assigned to the "web" middleware group. Make something great!
|
*/
Route::get('/', function () {
return view('welcome');
});
Route::get('demo', 'App¥Http¥Controllers¥DemoController@index');
Route::post('demo/spa_demo', 'App¥Http¥Controllers¥DemoController@spa_demo');
Auth::routes();
Route::get('/home', [App¥Http¥Controllers¥HomeController::class, 'index'])->name('home');
応用
useEffect(() => {
testRefresh(params) // params配列に変化があれば更新処理を発動させる。testRefreshはuseCallbackでメモ化してある。
}, [params])
useEffectはどのような場面で仕様されるか?
useEffectフックは多様な用途で使用されますが、一般的によく行われる処理をいくつか挙げます:使用例
import React, { useState, useEffect } from 'react';
function ExampleComponent() {
const [count, setCount] = useState(0);
// マウント時とcountが変わるたびに実行される
useEffect(() => {
document.title = `You clicked ${count} times`;
}, [count]); // 依存関係配列にcountを指定
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
使用例2:データのフェッチ (SPA, 旧AJAX)
useEffect(() => {
const fetchData = async () => {
try {
let postData = {'buta':'豚の高級住宅'};
const response = await axios.post('demo/spa_demo',postData);
if(typeof response.data === 'string'){
setErrorHtml(response.request.responseText);
}
setData(response.data);
} catch (error) {
console.error('Error fetching data:', error);
}
};
fetchData();
}, []);
import React, { useState, useCallback } from 'react';
// React.memoでメモ化された子コンポーネント
const Button = React.memo(({ onClick, children }) => {
console.log(`Rendering button: ${children}`);
return (
<button onClick={onClick}>{children}</button>
);
});
const App = () => {
const [count, setCount] = useState(0);
// useCallbackを使用してhandleClick関数をメモ化
const handleClick = useCallback(() => {
// 何らかの処理を実行(この例ではカウントを増やす)
setCount((prevCount) => prevCount + 1);
}, []); // 依存関係配列が空なので、コンポーネントが再レンダリングされても新しい関数は作成されない
return (
<div>
<p>Count: {count}</p>
<Button onClick={handleClick}>Increase Count</Button>
</div>
);
};
export default App;
この例では、handleClick関数はAppコンポーネントが再レンダリングされても再作成されません。React.memoによってメモ化されたButtonコンポーネントは、propsとして渡されたonClick関数が同一の参照である限り再レンダリングされません。useCallbackの依存配列が空なので、handleClickは一度作成されると再作成されることはありません。
useStateの見本
import React, { useState } from 'react';
function MyComponent() {
// useStateフックにより、count状態を初期値0で作成します。
// setCountはその状態を更新する関数です。
const [count, setCount] = useState(0);
// この関数は、ボタンがクリックされるたびに呼ばれます。
// これは、新しいcount値をReactに通知し、Reactはコンポーネントを再レンダリングします。
function handleClick() {
setCount(count + 1); // count状態を更新します。
}
// このUIは、コンポーネントが再レンダリングされるたびに更新されたcount値を表示します。
return (
<div>
<p>You clicked {count} times</p>
<button onClick={handleClick}>
Click me
</button>
</div>
);
}
上記のサンプルの場合、countとsetCountはMyComponent関数内の複数処理に対して共有される変数のようにふるまう。ローカルスコープのようなもの。