React 基础入门 (一)

前面的话

这是一篇算React入门级的文章,初次接触React,有问题请指出~~~。

安装方式

这里介绍搭建一个 react 项目的两种方式: CDN链接 和 官方推荐的 create-react-app 脚手架

CDN 链接

创建一个 index.html 文件,将 React、React DOM、Babel 这三个 CDN 链接引入到 head 标签中。并创建一个 div ,id为 root,作为根元素。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Hello React</title>
    <!-- React --- React顶级API -->
    <script src="https://unpkg.com/react@^16/umd/react.production.min.js"></script>
    <!-- React DOM --- 添加特定与DOM的方法 -->
    <script src="https://unpkg.com/[email protected]/umd/react-dom.production.min.js"></script>
    <!-- Babel ---js编译器,可使用es6+ -->
    <script src="https://unpkg.com/[email protected]/babel.js"></script>
</head>
<body>
    <div id="root"></div>
    <!--  text/babel 脚本类型是babel所必须的 -->
    <script type="text/babel">
        // react code
    </script>
</body>
</html>

引入这三个 CDN 链接的目的:

  • React – 顶级API
  • React DOM —添加特定DOM的方法
  • Babel — 种JavaScript编译器, 可使用ES6+

如果需要安装指定的版本,将@后面的版本号改为指定版本。

⚠️注意:script 脚本的类型 text/babel是必须的

我们都知道,不管是 Vue 还是 React 都是组件化,在 React 中有两种创建组件的方法,Class 组件和 Function 组件, 这里我们创建一个 Class 组件。

创建一个 Class 组件很简单,只要创建一个 Class 类,并让他继承 React.Component, 在render() 方法中 return 一个 JSX 模版,用于呈现 DOM节点。

⚠️注意:render() 方法是类组件唯一需要的方法。

创建组件App :

// 创建组件App
        class App extends React.Component {
            // render方法必须,用于呈现DOM节点
            render() {
                return (
                    // jsx 
                    <h1>hello word</h1>
                )
            }
        }
    

return 中, 可以看到返回很像 HTML 的内容,但其不是 HTML ,而是 JSX ,这里先不过多介绍。

最后,我们使用 ReactDOM.render() 方法将 App组件渲染到 root根元素 div 中:

    ReactDOM.render(<App/>, document.getElementById('root'));

index.html 的完整代码如下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Hello React</title>
    <!-- React --- React顶级API -->
    <script src="https://unpkg.com/react@^16/umd/react.production.min.js"></script>
    <!-- React DOM --- 添加特定与DOM的方法 -->
    <script src="https://unpkg.com/[email protected]/umd/react-dom.production.min.js"></script>
    <!-- Babel ---js编译器,可使用es6+ -->
    <script src="https://unpkg.com/[email protected]/babel.js"></script>
</head>
<body>
    <div id="root"></div>
    <!--  text/babel 脚本类型是babel所必须的 -->
    <script type="text/babel">
        // 创建组件App
        class App extends React.Component {
            // render方法必须,用于呈现DOM节点
            render() {
                return (
                    // jsx 
                    <h1>hello word</h1>
                )
            }
        }
        ReactDOM.render(<App/>, document.getElementById('root'));
    </script>
</body>
</html>

在浏览器中打开 index.html 文件:
在这里插入图片描述

这样一个简单的 React 应用就搭建好了。

create-react-app

第一种搭建 react 项目的方法只适合书写简单的react 代码,使用 create-react-app脚手架可以帮我们初始化一个项目 demo。

npx create-react-app  react-start

项目结构:public/index.html 是页面模板,src/index.js 则是入口JS文件

在这里插入图片描述
安装完成后,进入项目文件并启动:

cd react-start

npm start 

运行后,会弹出新窗口:
在这里插入图片描述

下面修改一下代码,修改App.js 文件为上面创建 App 组件的代码:

import React, { Component } from 'react';
// import './App.css';

class App extends Component {
  render() {
    return <h1>Hello, React</h1>;
  }
}
export default App;

在这里插入图片描述

什么是JSX?

上面的代码中已经接触了 JSX,看起来很像 HTML ,但是并不是。它是JSX,代表 JavaScript XML 。

接着上面的代码,修改App.js :

const name = 'React';
const element = (
  <div tabIndex="0" className="box">
    <h1>Hello, {name}</h1>
    <div className="name">
      <span>xiaoqi</span>
    </div>
  </div>
);
class App extends Component {
  render() {
    return element;
  }
}

element 就是一个 JSX,可以看到它的特点:

  • 可以使用 { } 内置 js 表达式
  • JSX 标签可以包含很多子标签
  • JSX 中可以指定特定的属性(采用大驼峰),比如上面的 tabIndexclassName

看一下渲染之后的效果:

在这里插入图片描述

元素渲染

元素是构成 React 应用的最小砖块。

创建一个元素 element :

const element = (
  <div className="box">
    <h1>React 入门</h1>
    <span> 元素是构成 React 应用的最小砖块。</span>
  </div>
);

将这个元素渲染到 DOM 根结点中,需要使用 ReactDOM.render() 方法:

ReactDOM.render(element, document.getElementById('root'));

因 index.js 文件是项目的 js 入口文件,所以我们修改 index.js 文件为上面的代码:

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
// import App from './App';
// import * as serviceWorker from './serviceWorker';

const element = (
  <div className="box">
    <h1>React 入门</h1>
    <span> 元素是构成 React 应用的最小砖块。</span>
  </div>
);
ReactDOM.render(element, document.getElementById('root'));

运行结果:
在这里插入图片描述

组件

组件分为两种: Class 组件和 Function 组件。

大多数的 React 应用都由很多小组件组成,将所有的小组件都添加到App主组件中,最后将 App主组件渲染到入口 js 文件 index.js 中。

我们将修改 App.jsindex.js 文件:

App.js:

import React, { Component } from 'react';
class App extends Component {
  render() {
    return (
      <div className="app">
        <h1> Hello, React</h1>
      </div>
    );
  }
}
export default App;

index.js:

import React from 'react'
import ReactDOM from 'react-dom'
import App from './App'
import './index.css'

ReactDOM.render(<App />, document.getElementById('root'))

Class 组件

前面提过,创建一个 Class 组件,只需要创建一个 class 类,并且让它继承React.Component, 在 render() 方法中 returnJSX模版。

接下来,我们在 src 文件夹中 ,创建一个 Table.js文件:

// Table组件
import React, { Component } from 'react';

class Table extends Component {
  render() {
    return (
      <table>
          <thead>
          <tr>
            <th>Name</th>
            <th>Job</th>
          </tr>
        </thead>
        <tbody>
          <tr>
            <td>Charlie</td>
            <td>Janitor</td>
          </tr>
          <tr>
            <td>Mac</td>
            <td>Bouncer</td>
          </tr>
          <tr>
            <td>Dee</td>
            <td>Aspiring actress</td>
          </tr>
      </table>
    );
  }
}
export default Table;

在 App.js中添加 Table组件:

class App extends Component {
  render() {
    return (
      <div className="app">
        <h1> Hello, React</h1>
        <Table />
      </div>
    );
  }
}

运行结果:

在这里插入图片描述

Function 组件

上面的 Table 组件, 我们可以拆分为两个子组件:TableBody组件和TableHeader组件, 这两个组件我们使用 Function 组件来创建:

修改 Table.js : TableHeaderTableBody组件都在Table.js文件中,并被 Table组件使用。

// TableHeader组件
const TableHeader = () => {
  return (
    <thead>
      <tr>
        <th>Name</th>
        <th>Job</th>
      </tr>
    </thead>
  );
};

// TableBody 组件
const TableBody = () => {
  return (
    <tbody>
      <tr>
        <td>Charlie</td>
        <td>Janitor</td>
      </tr>
      <tr>
        <td>Mac</td>
        <td>Bouncer</td>
      </tr>
      <tr>
        <td>Dee</td>
        <td>Aspiring actress</td>
      </tr>
    </tbody>
  );
};

class Table extends Component {
  render() {
    return (
      <table>
        <TableHeader />
        <TableBody />
      </table>
    );
  }
}

小结

比较一下 Function 组件与Class 组件:

Function 组件:

const functionComponent = () => {
	return  <div> Example</div>
}

Class 组件:

class ClassComponent entends React.Component {
	render() {
		return <div> Example</div>
	}
}

组件通信: Props

Vue中的父传子使用的是 props,React 中的父传子也是props,下面看一下在React中props是如何使用的。

如果表格的数据很多,那么上面的TableBody 组件会显的很臃肿。我们将< tbody>中要输出的数据提取出来,通过props的方式来进行传递。

修改App.js文件:

  • 在App组件中声明数据 characters, 表示< tbody>中要输出的数据。

  • 在子组件 Table上添加属性名称和数据

    class App extends Component {
      render() {
        // 提取数据
        const characters = [
          {
            name: 'Charlie',
            job: 'Janitor',
          },
          {
            name: 'Mac',
            job: 'Bouncer',
          },
          {
            name: 'Dee',
            job: 'Aspring actress',
          },
        ];
        return (
          <div className="app">
            <h1> Hello, React</h1>
            <Table characterData={characters} />
          </div>
        );
      }
    }
    

修改 Table.js :

  • 子组件中的使用:在 class 组件中通过 this.props.属性名来获取父组件中传递的数据:

    class Table extends Component {
      render() {
        const { characterData } = this.props;
        return (
          <table>
            <TableHeader />
            <TableBody characterData={characterData} />
          </table>
        );
      }
    }
    

    这里的 const { characterData } = this.props 相当于 const characterData = this.props.characterData,这里只是es6的写法。

  • 继续将数据传递给 Table 组件的子组件 TableBody: TableBody是一个 function组件,其使用 props 的方法直接是:将props作为参数名传递给function组件,使用 props.属性名即可获取父组件的数据

    // TableBody 组件
    const TableBody = (props) => {
      const rows = props.characterData.map((item, index) => {
        return (
          <tr key={index}>
            <td>{item.name}</td>
            <td>{item.job}</td>
          </tr>
        );
      });
      return <tbody>{rows}</tbody>;
    };
    

    上面的例子中,给每个表行都添加了一个 key。React 中创建列表时,使用都要使用key,来识别列表的每一项项,这是必要的。

⚠️注意props 具有只读性,无论是函数声明还是class声明的组件,都不能修改自身的props

State状态

props 是单向传递,并且是只读的。如果想改变数据的状态,可以使用state,与props 不同的是state可变,但是 state 是class组件私有的对象,修改state 不能直接修改,要使用this.setState() 方法来改变。

现在我们实现一个功能:在表的每行添加一个删除按钮,点击之后,可删除该行信息。

修改 App.js

  • 创建一个 state 对象,包含存储的数据 characters
  • 由于要删除数据,即要改变 state 对象,我们添加一个 removeCharacter() 方法,在该方法内实现 state 数据的变化,通过数组的索引过滤,然后返回新的数组。
  • 最后将该函数传递给子组件Table
class App extends Component {
  state = {
    characters: [
      {
        name: 'Charlie',
        job: 'Janitor',
      },
      {
        name: 'Mac',
        job: 'Bouncer',
      },
      {
        name: 'Dee',
        job: 'Aspring actress',
      },
    ],
  };
  // 箭头函数解决 class中this不绑定的问题
  removeCharacter = (i) => {
    this.setState({
      characters: this.state.characters.filter((item, index) => {
        return i !== index;
      }),
    });
  };

  render() {
    // 提取数据
    return (
      <div className="app">
        <h1> Hello, React</h1>
        <Table
          characterData={this.state.characters}
          removeCharacter={this.removeCharacter}
        />
      </div>
    );
  }
}

将函数removeCharactor传递给Table 组件之后,还需要传递给TableBody组件,并且还要在 TableBody组件中给每行添加一个按钮,点击之后执行此函数。

修改 Table.js:

// Table组件
import React, { Component } from 'react';

// TableHeader组件
const TableHeader = () => {
  return (
    <thead>
      <tr>
        <th>Name</th>
        <th>Job</th>
      </tr>
    </thead>
  );
};
// TableBody 组件
const TableBody = (props) => {
  const rows = props.characterData.map((item, index) => {
    return (
      <tr key={index}>
        <td>{item.name}</td>
        <td>{item.job}</td>
        <td>
          <button onClick={() => props.removeCharacter(index)}>Dalete</button>
        </td>
      </tr>
    );
  });
  return <tbody>{rows}</tbody>;
};

class Table extends Component {
  render() {
    const { characterData, removeCharacter } = this.props;
    return (
      <table>
        <TableHeader />
        <TableBody
          characterData={characterData}
          removeCharacter={removeCharacter}
        />
      </table>
    );
  }
}

该onClick函数必须通过返回该removeCharacter()方法的函数,否则它将尝试自动运行。

运行结果:
在这里插入图片描述

⚠️注意: State 的更新可能是异步的, React 可能会把多个setState() 调用合并成一个调用。

props 与 state的区别
  • state 是管理数据,控制状态,可变(通过this.setState() )
  • props 是外部传入的数据参数,不可变
  • 没有 state 的叫无状态组件,有 state 的叫有状态组件
  • 多用 props,少用 state。

事件处理

React 中事件处理和 DOM 元素的很相似,但是在语法上有些不同:

  • React 事件的命名采用小驼峰(camelCase)
  • 使用 JSX 语法时需要传入一个函数作为事件处理函数

例子:

<button onClick={activateLasers}>
  Activate Lasers
</button>

前面,我们已经实现了将数据存储在state状态中,并且可以从状态中删除任何数据。但如果我们想添加新的数据怎么办呢?

接下来,通过下面的表单例子,来体验 React 中的事件处理。我们添加一个 Form.js 组件, 每次更改表单中字段时会更新状态,并且在我们提交时,所有数据会传递给 App组件的 state,进而更新整个 Table。

创建新文件 Form.js :

  • 我们将表单的初始状态设置为带有空属性的对像,并且将初始状态赋给 this.state:

    import React, { Component } from 'react'
    
    class Form extends Component {
      initialState = {
        name: '',
        job: '',
      }
    
      state = this.initialState
    }
    
  • 表单内容大致如下:

    render() {
        const { name, job } = this.state; // 初始化时为空
        return (
          <form>
            <label htmlFor="name">name</label>
            <input
              type="text"
              name="name"
              id="name"
              value={name}
              onChange={this.handleChange}
            />
            <label htmlFor="job">job</label>
            <input
              type="text"
              name="job"
              id="job"
              value={job}
              onChange={this.handleChange}
            />
          </form>
        );
      }
    
  • 每次输入时都更改 state 里面的数据,所以在onChange 事件发生时,将 handleChangr() 方法作为回调传入

    handleChange = (event) => {
        const { name, value } = event.target;
        this.setState({
          [name]: value,
        });
      };
    

⚠️注意:

在class 组件中,class 的方法默认不会绑定 this,所以要谨慎 JSX 回调函数中的 this 。例如上面的例子中,如果忘记绑定 this ,直接将this.handleChange 传入 onChange,这时调用这个函数时, this将会是是undefined

绑定this的方法
  • 第一种:class 的方法使用箭头函数, 如上例。

    handleChange = (event) => {
        const { name, value } = event.target;
        this.setState({
          [name]: value,
        });
      };
    
  • 第二种: 在构造函数中使用 this.handleChange.bind(this) 进行绑定。

    constructor() {
        super();
        this.handleChange = this.handleChange.bind(this);
      }
    handleChange(event) {
    const { name, value } = event.target;
    this.setState({
      [name]: value,
    });
    }
    
  • 第三种:其实也相当于第二种,只不过不使用构造函数

    handleChange(event) {
    const { name, value } = event.target;
    this.setState({
      [name]: value,
    });
    
    // 直接在传入时进行绑定  
     onChange={this.handleChange.bind(this)}  
    

前面实现了表单的数据更新,最后一步是提交表单的数据并更新App组件的 state状态。

修改App.js:

创建一个名为handleSubmit 的函数,用来更新 state 的数据(添加表单提交的数据):

handleSubmit = (character) => {
    //  添加数据
    this.setState({
      characters: [...this.state.characters, character],
    });
  };

将其传给 Form 组件:

render() {
    // 提取数据
    return (
      <div className="app">
        <h1> Hello, React</h1>
        <Table
          characterData={this.state.characters}
          removeCharacter={this.removeCharacter}
        />
        <Form handleSubmit={this.handleSubmit} />
      </div>
    );
  }

接下来,在 Form.js 中创建名为 submitForm(),该方法将调用 父组件传递的方法 handleSubmit。将Form组件的 state作为参数传入:

修改 Form,js:

// 提交表单
  submitForm = () => {
    // 添加数据
    this.props.handleSubmit(this.state);
    // 添加之后清空state
    this.setState(this.initialState);
  };

最后,我们为表单添加一个提交按钮用来提交表单:

        <input type="button" value="submit" onClick={this.submitForm} />

⚠️注意:这里的submitFrom方法也得绑定this

运行项目,具体的功能就实现了:
在这里插入图片描述

条件渲染

React 中的条件渲染和javaScript 中的一样,可以使用 运算符 if 或者条件运算。

下面实现一个登录/登出按钮,根据不同的状态现实不同的效果。为了方便,还是在上面的项目实现这效果。

添加一个Login.js文件:

  • 创建两个组件:LoginButtonLogoutButton 分别代表登录和注销。

    // 登录
    const LoginButton = (props) => {
      return <button onClick={props.click}>Login</button>;
    };
    
    // 注销
    const LogoutButton = (props) => {
      return <button onClick={props.click}>Logout</button>;
    };
    
  • 创建一个有状态的组件LoginControl,用来根据当前的状态来渲染上面的两个组件。

class LoginControl extends Component {
  // 初始状态
  state = {
    isLoggedIn: false,
  };
  
  handleLoginClick = () => {
    this.setState({
      isLoggedIn: true,
    });
  };
  handleLogoutClick = () => {
    this.setState({
      isLoggedIn: false,
    });
  };

  render() {
    const isLoggedIn = this.state.isLoggedIn;
    let button;
    // 通过if 语句来进行条件渲染
    if (isLoggedIn) {
      button = <LoginButton click={this.handleLogoutClick} />;
    } else {
      button = <LogoutButton click={this.handleLoginClick} />;
    }
    return (
      <div>
        {button}
      </div>
    );
  }
}
  • 为了更好的看到效果,我们每次切换后,加上对应的描述。创建两个组件,代表切换后对应的描述:
// 登录
const LoginGreeting = () => {
  return <span>Welcome </span>;
};
// 注销
const LogoutGreeting = () => {
  return <span>Please sign up</span>;
};
  • 创建一个组件Geeting ,来渲染上面的描述组件.这里使用三目运算符来实现
const Greeting = (props) => {
  const { isLoggedIn } = props;
  return <div>{isLoggedIn ? <LoginGreeting /> : <LogoutGreeting />}</div>;
};
  • 最后,在LoginControl 组件中加入这个 Greeting组件,同时在App 组件中加入LoginControl 组件。
// ...
return (
      <div>
        {button}
        <Greeting isLoggedIn={isLoggedIn} />
      </div>
  // ...

App加入LoginControl组件:

// ...
render() {
    // 提取数据
    return (
      <div className="app">
        <h1> Hello, React</h1>
        <Table
          characterData={this.state.characters}
          removeCharacter={this.removeCharacter}
        />
        <Form handleSubmit={this.handleSubmit} />
        <LoginControl />
      </div>
    );
  }
  // ...

运行项目(样式有点丑~):

在这里插入图片描述

列表 & key

前面的例子 TableBody组件中 ,已经接触了列表:

const TableBody = (props) => {
  const rows = props.characterData.map((item, index) => {
    return (
      <tr key={index}>
        <td>{item.name}</td>
        <td>{item.job}</td>
        <td>
          <button onClick={() => props.removeCharacter(index)}>Dalete</button>
        </td>
      </tr>
    );
  });
  return <tbody>{rows}</tbody>;
};

这个组件中,rowstr的数组,最后将rows 插入到 < tbody> 元素中。

这里可以看到每一个 tr 都有一个特殊的属性key,这个key是必要的(这与diff算法有关)。

key 帮助 React 识别哪些元素改变了,比如删除或者添加。所以应当给数组中的每一个元素给予一个确定的标识。一个元素的key最好是这个元素在列表中拥有的独一无二的字符串。通常使用数据的id来作为元素的 key。如果元素没有确定的 id时,可以使用元素索引 index 作为 key

⚠️注意:

  • 元素的key只有放在就近的数组上下文中才有意义。如上例中的map 方法中的元素需要设置 key 属性
  • key 值只是在兄弟节点间独一无二,而不是全局的唯一。(也就是说,生成两个不同的数组时,可以使用相同的key

生命周期

在这里插入图片描述

挂载

当组件实例被创建并插入 DOM中,其生命周期调用顺序如下:

  • constructor: 构造函数初始化,最先被执行,初始化 state
  • getDerivedStateFromProps :这是一个静态方法,需要在前面添加static属性
  • render: 渲染函数,返回渲染的内容,当页面更新时也会触发
  • componentDidMount: 组件挂载之后,这个时候组件已经挂载完毕了
更新
  • getDerivedStateFromProps : 组件即将被更新,这里参数分别对应前后被修改的内容,通过返回一个布尔值告知是否要更新视图。
  • render: 当视图更新,那么render 也会更新
  • getSnapshotBeforeUpdate: getSnapshotBeforeUpdaterender之后componentDidUpdate之前输出,类似于中间件用来做一些捕获操作
  • componentDidUpdategetSnapshotBeforeUpdate,有三个参数prevPropsprevStatesnapshot,表示之前的 props,之前的 state,和snapshotsnapshotgetSnapshotBeforeUpdate返回的值

卸载

  • componentWillUnmount: 组件卸载,可以清除一些定时器,取消网络请求。

修改 App.js 代码:

 class App extends Component {
  constructor() {
    super();
    console.log('1: constructor初始化');
  }
  state = {
    characters: [
      {
        name: 'Charlie',
        job: 'Janitor',
      },
      {
        name: 'Mac',
        job: 'Bouncer',
      },
      {
        name: 'Dee',
        job: 'Aspring actress',
      },
    ],
  };

  // 移除项目
  removeCharacter = (i) => {
    this.setState({
      characters: this.state.characters.filter((item, index) => {
        return i !== index;
      }),
    });
  };

  handleSubmit = (character) => {
    //  添加数据
    this.setState({
      characters: [...this.state.characters, character],
    });
  };

  static getDerivedStateFromProps(nextProps, prevState) {
    console.log('getDerivedStateFromProps: 组件将要被更新');
    console.log(prevState);
    return true;
  }

  componentDidMount() {
    console.log('componentDidMount 组件挂载完毕');
  }

  getSnapshotBeforeUpdate(preProps, prevState) {
    console.log(preProps, prevState);
    return 'top: 200';
  }
  
  componentDidUpdate(preProps, prevState, snapshot) {
    console.log('组件更新完毕');
    console.log(preProps, prevState, snapshot);
  }
  render() {
    // 提取数据
    return (
      <div className="app">
        <h1> Hello, React</h1>
        <Table
          characterData={this.state.characters}
          removeCharacter={this.removeCharacter}
        />
        <Form handleSubmit={this.handleSubmit} />
        <LoginControl />
      </div>
    );
  }
}       

运行结果:
在这里插入图片描述

总结

学习React很多都是以Vue的思路先入个门,很多东西都是类似的。第一篇入门文章就到这里,后面继续~~。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章