Web 学習ノート - React (結合コンポーネント)

メモの内容は、AcWing の Web アプリケーション コース配布資料 (コース リンク: AcWing Web アプリケーション コース )から転載されています。

このセクションの内容は、異なるコンポーネントを使用して DOM ツリーを形成する、異なるコンポーネントにデータを渡す、異なるコンポーネントのメソッドを呼び出すなどのコンポーネントの組み合わせと、異なるコンポーネントのライフサイクルです。

1. 親コンポーネントを作成する

前のコンポーネントBoxの作業を続けます。まずBoxes、一連のBoxコンポーネントを含むコンポーネントを作成します。

componentsディレクトリに作成しますboxes.jsx:

import React, {
    
     Component } from 'react';

class Boxes extends Component {
    
    
    state = {
    
      } 
    render() {
    
     
        return (
            <h1>Boxes</h1>
        );
    }
}
 
export default Boxes;

次に、それを変更しますindex.js

import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import 'bootstrap/dist/css/bootstrap.css';
import Boxes from './components/boxes';

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<Boxes />);

次に、Boxesに複数の要素を追加しますBox。コンポーネントに複数の並列要素が含まれる場合、それらをタグで囲む必要があります。React では仮想タグを使用できます<React.Fragment>

import React, {
    
     Component } from 'react';
import Box from './box';

class Boxes extends Component {
    
    
    state = {
    
      } 
    render() {
    
     
        return (
            <React.Fragment>
                <Box />
                <Box />
                <Box />
            </React.Fragment>
        );
    }
}
 
export default Boxes;

便宜上、配列で表すこともでき、 のBox情報は に保存されますstate。React コンポーネントには複数の子があり、それぞれのkeyニーズが異なるため、一意の子を保存する必要がありますid

import React, {
    
     Component } from 'react';
import Box from './box';

class Boxes extends Component {
    
    
    state = {
    
     
        boxes: [
            {
    
    id: 1, x: 0},
            {
    
    id: 2, x: 0},
            {
    
    id: 3, x: 0},
            {
    
    id: 4, x: 0},
        ]
     } 
    render() {
    
     
        return (
            <React.Fragment>
                {
    
    this.state.boxes.map(box => (
                    <Box key={
    
    box.id} />
                ))}
            </React.Fragment>
        );
    }
}
 
export default Boxes;

2. データを上から下に渡す

データは属性を通じてthis.props上から下に渡すことができます。たとえば、次のようBoxesに渡しますx

...

class Boxes extends Component {
    
    
    state = {
    
     
        ...
     } 
    render() {
    
     
        return (
            <React.Fragment>
                {
    
    this.state.boxes.map(box => (
                    <Box key={
    
    box.id} x={
    
    box.x} name='yyj' />
                ))}
            </React.Fragment>
        );
    }
}
 
export default Boxes;

Box情報はView contentに出力できますconsole.log(this.props);

ここに画像の説明を挿入します

修正Boxx:

import React, {
    
     Component } from 'react';  // 输入imrc即可补全

class Box extends Component {
    
      // 输入cc即可补全
    state = {
    
     
        x: this.props.x,
    };

    ...
}
 
export default Box;

3. 子ノードを渡す

次の形式でタグを記述し<Box></Box>、そのタグにサブタグを追加できます。

import React, {
    
     Component } from 'react';
import Box from './box';

class Boxes extends Component {
    
    
    state = {
    
     
        boxes: [
            {
    
    id: 1, x: 0},
            {
    
    id: 2, x: 0},
            {
    
    id: 3, x: 0},
            {
    
    id: 4, x: 0},
        ]
     } 
    render() {
    
     
        return (
            <React.Fragment>
                {
    
    this.state.boxes.map(box => (
                    <Box key={
    
    box.id} x={
    
    box.x} name='yyj'>
                        <h1>Title</h1>
                    </Box>
                ))}
            </React.Fragment>
        );
    }
}
 
export default Boxes;

このようにして、this.props属性が 1 つ増えchildren、それを使用して[]サブタグに個別にアクセスできるようになります。この渡された値はどこにでも定義できます。たとえば、Box各コンポーネントの先頭に配置できます。

import React, {
    
     Component } from 'react';  // 输入imrc即可补全

class Box extends Component {
    
      // 输入cc即可补全
    state = {
    
     
        x: this.props.x,
    };

    handleClickLeft = (step) => {
    
    
        this.setState({
    
    
            x: this.state.x - step
        });
    }

    handleClickRight = (step) => {
    
    
        this.setState({
    
    
            x: this.state.x + step
        });
    }

    handleClickLeftTmp = () => {
    
    
        this.handleClickLeft(10);
    }

    render() {
    
      // Component类的函数,用来返回当前组件最后渲染的HTML结构是什么
        console.log(this.props);
        return (
        // HTML标签中可以使用{}写一个表达式
        <React.Fragment>
            {
    
    this.props.children}
            <div style={
    
    this.getStyles()}>{
    
    this.state.x}</div>
            <button onClick={
    
    this.handleClickLeftTmp} className='btn btn-primary m-2'>Left</button>
            <button onClick={
    
    () => this.handleClickRight(10)} className='btn btn-success m-2'>Right</button>
        </React.Fragment>
        );
    }

    getStyles() {
    
    
        ...
    }
}
 
export default Box;

4. 関数を下から上に呼び出す

親要素はthis.propsを介し​​て子要素に情報を渡すことができ、子要素は関数を使用して親要素に情報を渡すこともできます。削除ボタン をクリックして誰かを削除する必要がありBox、その情報は に保存されていますがBoxesstateクリックによってトリガーされたイベントは にあるとしますBox(注: 各コンポーネントはthis.stateコンポーネント内でのみ変更でき、他のコンポーネントは変更できません)。

親要素で関数を定義し、その関数を子要素に渡すことができます。

import React, {
    
     Component } from 'react';
import Box from './box';

class Boxes extends Component {
    
    
    state = {
    
     
        boxes: [
            {
    
    id: 1, x: 0},
            {
    
    id: 2, x: 0},
            {
    
    id: 3, x: 0},
            {
    
    id: 4, x: 0},
        ]
    }

    handleDelete = (boxId) => {
    
    
        // 遍历一遍state.boxes,将box.id不为传入的参数boxId的数据保留下来
        const res = this.state.boxes.filter(box => box.id !== boxId);
        this.setState({
    
    boxes: res});
    }

    render() {
    
     
        return (
            <React.Fragment>
                {
    
    this.state.boxes.map(box => (
                    <Box key={
    
    box.id} id={
    
    box.id} x={
    
    box.x} name='yyj'
                        onDelete={
    
    this.handleDelete}
                    />
                ))}
            </React.Fragment>
        );
    }
}
 
export default Boxes;

このようにして、子要素は親要素を操作する関数を呼び出すことができます。

import React, {
    
     Component } from 'react';  // 输入imrc即可补全

class Box extends Component {
    
      // 输入cc即可补全
    ...

    render() {
    
      // Component类的函数,用来返回当前组件最后渲染的HTML结构是什么
        console.log(this.props);
        return (
        // HTML标签中可以使用{}写一个表达式
        <React.Fragment>
            ...
            <button onClick={
    
    () => this.props.onDelete(this.props.id)} className='btn btn-danger m-2'>Delete</button>
        </React.Fragment>
        );
    }

    getStyles() {
    
    
        ...
    }
}
 
export default Box;

ここで、すべてをBoxesクリアするために [リセット] ボタンを実装しますBoxx

import React, {
    
     Component } from 'react';
import Box from './box';

class Boxes extends Component {
    
    
    state = {
    
     
        boxes: [
            {
    
    id: 1, x: 0},
            {
    
    id: 2, x: 1},
            {
    
    id: 3, x: 2},
            {
    
    id: 4, x: 3},
        ]
    }

    handleDelete = (boxId) => {
    
    
        ...
    }

    handleReset = () => {
    
    
        const res = this.state.boxes.map(box => {
    
    
            return {
    
    
                id: box.id,
                x: 0,
            }
        });
        this.setState({
    
    boxes: res});
    }

    render() {
    
    
        console.log(this.state.boxes);
        return (
            <React.Fragment>
                <button
                    onClick={
    
    this.handleReset}
                    style={
    
    {
    
    marginBottom: '15px'}}
                    className='btn btn-info'
                >Reset</button>
                {
    
    this.state.boxes.map(box => (
                    <Box key={
    
    box.id} id={
    
    box.id} x={
    
    box.x} name='yyj'
                        onDelete={
    
    this.handleDelete}
                    />
                ))}
            </React.Fragment>
        );
    }
}
 
export default Boxes;

xコンソールで観察すると、[リセット] ボタンをクリックした後、 は確かにゼロに設定されていますが、Box表示は変化していないことがわかります。xこれは、値を外部から変更できないためです。そのため、 in を削除stateでき、レンダリングする必要があります。コンポーネント内の外部値。Boxstatestate

保持されている各データは 1 つの にのみ保存できます。関数によって変更が上書きされる可能性があるため、this.stateの値を直接変更しないでください。this.statesetState

前の操作で使用した関数を変更しBoxesて転送しますBoxstate

import React, {
    
     Component } from 'react';
import Box from './box';

class Boxes extends Component {
    
    
    state = {
    
     
        boxes: [
            {
    
    id: 1, x: 0},
            {
    
    id: 2, x: 1},
            {
    
    id: 3, x: 2},
            {
    
    id: 4, x: 3},
        ]
    }

    handleDelete = (boxId) => {
    
    
        // 遍历一遍state.boxes,将box.id不为传入的参数boxId的数据保留下来
        const res = this.state.boxes.filter(box => box.id !== boxId);
        this.setState({
    
    boxes: res});
    }

    handleReset = () => {
    
    
        const res = this.state.boxes.map(box => {
    
    
            return {
    
    
                id: box.id,
                x: 0,
            }
        });
        this.setState({
    
    boxes: res});
    }

    // 需要知道修改的是哪个box
    handleClickLeft = (box) => {
    
    
        const boxes = [...this.state.boxes];  // 浅拷贝一份
        const k = boxes.indexOf(box);  // 传入的box是引用,找出其在boxes中的下标k
        boxes[k] = {
    
    ...boxes[k]};  // 再clone一遍,相当于创建新的state,深拷贝
        boxes[k].x--;
        this.setState({
    
    boxes: boxes});
    }

    handleClickRight = (box) => {
    
    
        const boxes = [...this.state.boxes];
        const k = boxes.indexOf(box);
        boxes[k] = {
    
    ...boxes[k]};
        boxes[k].x++;
        this.setState({
    
    boxes: boxes});
    }

    render() {
    
    
        return (
            <React.Fragment>
                <button
                    onClick={
    
    this.handleReset}
                    style={
    
    {
    
    marginBottom: '15px'}}
                    className='btn btn-info'
                >Reset</button>

                {
    
    this.state.boxes.map(box => (
                    <Box key={
    
    box.id} id={
    
    box.id} x={
    
    box.x} name='yyj'
                        onDelete={
    
    this.handleDelete}
                        onClickLeft={
    
    () => this.handleClickLeft(box)}
                        onClickRight={
    
    () => this.handleClickRight(box)}
                    />
                ))}
            </React.Fragment>
        );
    }
}
 
export default Boxes;

次に、それを変更しBoxthis.state親コンポーネントによって渡されたものと置き換えますprops

import React, {
    
     Component } from 'react';  // 输入imrc即可补全

class Box extends Component {
    
      // 输入cc即可补全
    render() {
    
      // Component类的函数,用来返回当前组件最后渲染的HTML结构是什么
        return (
        // HTML标签中可以使用{}写一个表达式
        <React.Fragment>
            <div style={
    
    this.getStyles()}>{
    
    this.props.x}</div>
            <button onClick={
    
    this.props.onClickLeft} className='btn btn-primary m-2'>Left</button>
            <button onClick={
    
    this.props.onClickRight} className='btn btn-success m-2'>Right</button>
            <button onClick={
    
    () => this.props.onDelete(this.props.id)} className='btn btn-danger m-2'>Delete</button>
        </React.Fragment>
        );
    }

    getStyles() {
    
    
        let styles = {
    
    
            width: '50px',
            height: '50px',
            backgroundColor: 'lightblue',
            color: 'white',
            textAlign: 'center',
            lineHeight: '50px',
            borderRadius: '5px',
            position: 'relative',
            left: this.props.x
        };

        if (this.props.x === 0) {
    
    
            styles.backgroundColor = 'orange';
        }

        return styles;
    }
}
 
export default Box;

5. 兄弟コンポーネント間でメッセージを渡す

コンポーネントの構造的関係がより複雑な場合、複数のコンポーネントによって共有されるデータは、最も近い共通の祖先this.stateに保存する必要があります。

App2 つのサブコンポーネントNavBar(ナビゲーション バー) と を含むコンポーネントを作成しますBoxes。これら 2 つのコンポーネントは兄弟コンポーネントです。

1つ目は次のとおりですnavbar.jsx

import React, {
    
     Component } from 'react';

class NavBar extends Component {
    
    
    state = {
    
      } 
    render() {
    
     
        return (
            <nav className="navbar bg-body-tertiary">
                <div className="container-fluid">
                    <a className="navbar-brand" href="/">Navbar</a>
                </div>
            </nav>
        );
    }
}
 
export default NavBar;

そしてapp.jsx、:

import React, {
    
     Component } from 'react';
import NavBar from './navbar';
import Boxes from './boxes';

class App extends Component {
    
    
    state = {
    
      } 
    render() {
    
     
        return (
            <React.Fragment>
                <div className='container'>
                    <NavBar />
                    <Boxes />
                </div>
            </React.Fragment>
        );
    }
}
 
export default App;

ここで、いくつかのコンポーネントに関する情報をNavBarに保存したいとします。その場合、その情報は、これら 2 つのコンポーネントの最新の共通祖先にのみ配置できます。BoxesBoxApp

に関連するすべてのコンテンツを次の場所に移動BoxesますstateApp

import React, {
    
     Component } from 'react';
import Box from './box';

class Boxes extends Component {
    
    
    render() {
    
    
        return (
            <React.Fragment>
                <button
                    onClick={
    
    this.props.onReset}
                    style={
    
    {
    
    marginBottom: '15px'}}
                    className='btn btn-info'
                >Reset</button>

                {
    
    this.props.boxes.map(box => (
                    <Box key={
    
    box.id} id={
    
    box.id} x={
    
    box.x} name='yyj'
                        onDelete={
    
    this.props.onDelete}
                        onClickLeft={
    
    () => this.props.onClickLeft(box)}
                        onClickRight={
    
    () => this.props.onClickRight(box)}
                    />
                ))}
            </React.Fragment>
        );
    }
}
 
export default Boxes;

移動後はApp以下の通りです。

import React, {
    
     Component } from 'react';
import NavBar from './navbar';
import Boxes from './boxes';

class App extends Component {
    
    
    state = {
    
     
        boxes: [
            {
    
    id: 1, x: 0},
            {
    
    id: 2, x: 1},
            {
    
    id: 3, x: 2},
            {
    
    id: 4, x: 3},
        ]
    }

    handleDelete = (boxId) => {
    
    
        // 遍历一遍state.boxes,将box.id不为传入的参数boxId的数据保留下来
        const res = this.state.boxes.filter(box => box.id !== boxId);
        this.setState({
    
    boxes: res});
    }

    handleReset = () => {
    
    
        const res = this.state.boxes.map(box => {
    
    
            return {
    
    
                id: box.id,
                x: 0,
            }
        });
        this.setState({
    
    boxes: res});
    }

    // 需要知道修改的是哪个box
    handleClickLeft = (box) => {
    
    
        const boxes = [...this.state.boxes];  // 浅拷贝一份
        const k = boxes.indexOf(box);  // 传入的box是引用,找出其在boxes中的下标k
        boxes[k] = {
    
    ...boxes[k]};  // 再clone一遍,相当于创建新的state,深拷贝
        boxes[k].x--;
        this.setState({
    
    boxes: boxes});
    }

    handleClickRight = (box) => {
    
    
        const boxes = [...this.state.boxes];
        const k = boxes.indexOf(box);
        boxes[k] = {
    
    ...boxes[k]};
        boxes[k].x++;
        this.setState({
    
    boxes: boxes});
    }

    render() {
    
     
        return (
            <React.Fragment>
                <div className='container'>
                    <NavBar
                        boxesCount={
    
    this.state.boxes.length}  // 将长度传给NavBar
                    />
                    <Boxes
                        boxes={
    
    this.state.boxes}
                        onReset={
    
    this.handleReset}
                        onClickLeft={
    
    this.handleClickLeft}
                        onClickRight={
    
    this.handleClickRight}
                        onDelete={
    
    this.handleDelete}
                    />
                </div>
            </React.Fragment>
        );
    }
}
 
export default App;

の長さ情報を でNavBar読み取ることができるようになりました。Boxes

import React, {
    
     Component } from 'react';

class NavBar extends Component {
    
    
    state = {
    
      } 
    render() {
    
     
        return (
            <nav className="navbar bg-body-tertiary">
                <div className="container-fluid">
                    <a className="navbar-brand" href="/">
                        Navbar <span>Boxes Count: {
    
    this.props.boxesCount}</span>
                    </a>
                </div>
            </nav>
        );
    }
}
 
export default NavBar;

6. ステートレス機能コンポーネント

コンポーネント内で が使用されていない場合は、ステートレス関数コンポーネントthis.stateと略すことができます関数に対するクラスの最大の利点は、状態 (ローカル変数) を簡単に維持できることです。

ステートレス機能コンポーネントなので、入力をsfc自動で完了することができます。render関数コンポーネントは、関数のみを備えたクラスコンポーネントと同等です。注: 関数の受信パラメータはpropsオブジェクトです。

import React from 'react';

const NavBar = (props) => {
    
    
    return (
        <nav className="navbar bg-body-tertiary">
            <div className="container-fluid">
                <a className="navbar-brand" href="/">
                    Navbar <span>Boxes Count: {
    
    props.boxesCount}</span>
                </a>
            </div>
        </nav>
    );
}
 
export default NavBar;

7. コンポーネントのライフサイクル

  • Mountサイクル (マウント、オブジェクトが作成されたことを示す)、実行シーケンス (3 つの関数を順番に実行):constructor() -> render() -> componentDidMount()
  • Updateサイクル (ボタンのクリックなどの変更)、実行シーケンス:render() -> componentDidUpdate()
  • Unmountサイクル(削除)、実行順序:componentWillUnmount()

このうち、関数には 2 つのパラメータがあり、componentDidUpdateそれぞれ更新propsと更新前を表しますstate

import React, {
    
     Component } from 'react';
import NavBar from './navbar';
import Boxes from './boxes';

class App extends Component {
    
    
    state = {
    
     
        boxes: [
            {
    
    id: 1, x: 0},
            {
    
    id: 2, x: 1},
            {
    
    id: 3, x: 2},
            {
    
    id: 4, x: 3},
        ]
    }

    componentDidUpdate(prevProps, prevState) {
    
    
        console.log('App - Update');
        console.log(prevProps, this.props);
        console.log(prevState, this.state);
    }

    ...

    render() {
    
    
        console.log('App - Render');
        return (
            ...
        );
    }
}
 
export default App;

出力state内容は以下のとおりです。

{boxes: Array(4)}
	boxes: Array(4)
		0: {id: 1, x: 0}
		1: {id: 2, x: 1}
		2: {id: 3, x: 2}
		3: {id: 4, x: 3}
		length: 4
		[[Prototype]]: Array(0)
	[[Prototype]]: Object

{boxes: Array(4)}
	boxes: Array(4)
		0: {id: 1, x: 1}  (此处有区别)
		1: {id: 2, x: 1}
		2: {id: 3, x: 2}
		3: {id: 4, x: 3}
		length: 4
		[[Prototype]]: Array(0)
	[[Prototype]]: Object

おすすめ

転載: blog.csdn.net/m0_51755720/article/details/132780159