React学习

发布于 2021-07-10  320 次阅读


React

React 是一个用于构建用户界面的 JavaScript 库。

本文基本参照react核心概念

仅作为个人笔记和记录,csdn爬虫别爬了

Hello World

它将在页面上展示一个 “Hello, world!” 的标题。

ReactDOM.render(
  <h1>Hello, world!</h1>,
  document.getElementById('root')
);

JSX

const element = <h1>Hello, world!</h1>;

这个有趣的标签语法既不是字符串也不是 HTML。

它被称为 JSX,是一个 JavaScript 的语法扩展。我们建议在 React 中配合使用 JSX,JSX 可以很好地描述 UI 应该呈现出它应有交互的本质形式。JSX 可能会使人联想到模版语言,但它具有 JavaScript 的全部功能。

JSX嵌入表达式

因为 JSX 语法上更接近 JavaScript 而不是 HTML,所以 React DOM 使用 camelCase(小驼峰命名)来定义属性的名称,而不使用 HTML 属性名称的命名约定。

在下面的例子中,我们声明了一个名为 name 的变量,然后在 JSX 中使用它,并将它包裹在大括号中:

const name = 'ki9mu';
const element = <h1>Hello, {name}</h1>;
ReactDOM.render(
  element,
  document.getElementById('root')
);

XSS虽迟但到

const name1 = 'a';
const a = alert`xss2`;
const element = <h1>Hello, {{name1}}</h1>;

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

函数形式

function formatName(user) {
  return user.firstName + ' ' + user.lastName;
}

const user = {
  firstName: 'ki9mu',
  lastName: 'admin'
};

const element = (
  <h1>
    Hello, {formatName(user)}!  </h1>
);

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

加载图片

const user = {
    firstName: "ki9mu",
    lastName: "admin",
    avatarUrl: "https://ki9mu.cn/wp-content/themes/Sakura/images/favicon.ico"
}
const element = <img src={user.avatarUrl} />;

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

JSX 表示对象

Babel 会把 JSX 转译成一个名为 React.createElement() 函数调用。

以下三种示例代码完全等效:

const element = (
  <h1 className="greeting">
    Hello, world!
  </h1>
);
const element = React.createElement(
  'h1',
  {className: 'greeting'},
  'Hello, world!'
);

元素渲染

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

元素描述了你在屏幕上想看到的内容。

const element = <h1>Hello, world</h1>;

与浏览器的DOM元素不同,React元素开销极小的普通对象。

将一个元素渲染为DOM

想要将一个 React 元素渲染到根 DOM 节点中,只需把它们一起传入 ReactDOM.render()

const element = <h1>Hello, world</h1>;
ReactDOM.render(element, document.getElementById('root'));

更新已渲染的元素

React元素是不可变对象。一旦被创建就无法更改子元素和属性。

更新UI可以考虑setInterval

定时器实例

function tick(){
  const element = (
    <div>
      <h1>Hello world!</h1>
      <h2>It is {new Date().toLocaleTimeString()}</h2>      
    </div>
  );
  ReactDOM.render(element,document.getElementById('root'));
}
setInterval(tick, 2000);

这个例子会在 setInterval() 回调函数,每秒都调用 ReactDOM.render()

组件

组件允许你将 UI 拆分为独立可复用的代码片段,并对每个片段进行独立构思。

人话:就是函数

函数组件与class组件

定义组件最简单的方式就是编写 JavaScript 函数:

function Welcome(props) {
  return <h1>Hello, {props.name}</h1>;
}

同时还可以使用 ES6 的 class 来定义组件:

class Welcome extends React.Component {
  render() {
    return <h1>Hello, {this.props.name}</h1>;
  }
}

上述两个组件在 React 里是等效的。

渲染组件

用户自定义的组件

const element = <Welcome name="Sara" />;

当 React 元素为用户自定义组件时,它会将 JSX 所接收的属性(attributes)以及子组件(children)转换为单个对象传递给组件,这个对象被称之为 “props”。

function Login(props) {
  const a = (
    <form>
      <input name="username" value={props.username} type="text"/>
      <input name="password" value={props.password} type="password"/>
      <input name="button" value="提交" type="submit"/>
    </form>
  );
  return a;
}

const element = <Login username="Sara" password="23" />;
ReactDOM.render(element, document.getElementById('root'));

注意: 组件名称必须以大写字母开头。

React将小写认为是原生标签,大写为组件

多种组件

组件可以在其输出中引用其他组件。这就可以让我们用同一组件来抽象出任意层次的细节。按钮,表单,对话框,甚至整个屏幕的内容:在 React 应用程序中,这些通常都会以组件的形式表示。

人话:多个函数互相调用

function Input(props) {
  const a = <input name={props.name} value={props.value} type={props.type}/>;
  return a;
}

function Login(props) {
  const form = (
    <form>
      <Input name="username" value={props.usernameValue} type="text"/>
      <Input name="password" value={props.passwordValue} type="password"/>
      <Input name="button" value={props.submitValue} type="submit"/>
    </form>
  );
  return form;
}
const element = <Login usernameValue="ki9mu" passwordValue="123456" submitValue="提交"/>;
ReactDOM.render(element, document.getElementById('root'));

Props 的只读性

无论是使用函数声明还是class声明,无法改变props的值

以下结果仍然是ki9mu

function Test(props) {
 props.num = 'admin'
 return <h1>{props.num}</h1>;
}
const element = <Test num='ki9mu'/>;
ReactDOM.render(element, document.getElementById('root'));

State&生命周期

魔改之前的定时器

function tick(){
  const element = (
    <div>
      <h1>Hello world!</h1>
      <h2>It is {new Date().toLocaleTimeString()}</h2>      
    </div>
  );
  ReactDOM.render(element,document.getElementById('root'));
}
setInterval(tick, 2000);

做拆分

function Clock(props){
  const element = (
    <div>
      <h1>Hello world!</h1>
      <h2>It is {props.time.toLocaleTimeString()}</h2>      
    </div>
  );
    return element;
}
function tick(){
  const element = <Clock time={new Date()}/>
  ReactDOM.render(element,document.getElementById('root'));
}
setInterval(tick,1000)

将函数组件转换成 class 组件

  1. 创建一个同名的 ES6 class,并且继承于 React.Component
  2. 添加一个空的 render() 方法。
  3. 将函数体移动到 render() 方法之中。
  4. render() 方法中使用 this.props 替换 props
  5. 删除剩余的空函数声明。
class Clock extends React.Component {
    render(){
          const element = (
            <div>
              <h1>Hello world!</h1>
              <h2>It is {props.time.toLocaleTimeString()}</h2>      
            </div>
          );
          return element;
    }
}
function tick(){
  const element = <Clock time={new Date()}/>
  ReactDOM.render(element,document.getElementById('root'));
}
setInterval(tick,1000)

现在 Clock 组件被定义为 class,而不是函数。

每次组件更新时 render 方法都会被调用,但只要在相同的 DOM 节点中渲染 <Clock /> ,就仅有一个 Clock 组件的 class 实例被创建使用。这就使得我们可以使用如 state 或生命周期方法等很多其他特性。

向 class 组件中添加局部的 state

我们通过以下三步将 date 从 props 移动到 state 中:

  1. render() 方法中的 this.props.time 替换成 this.state.time
  2. 添加一个 class 构造函数,然后在该函数中为 this.state 赋初值:
  3. 移除 <Clock /> 元素中的 date 属性:
class Clock extends React.Component {
    constructor(props) {
    super(props);
    this.state = {time: new Date()};
    }
    render(){
          const element = (
            <div>
              <h1>Hello world!</h1>
              <h2>It is {this.state.time.toLocaleTimeString()}</h2>      
            </div>
          );
          return element;
    }
}
function tick(){
  const element = <Clock />
  ReactDOM.render(element,document.getElementById('root'));
}
setInterval(tick,1000)

这构造函数是真的丑

constructor( ) ----> 构造方法

​ 这是ES6对类的默认方法,通过 new 命令生成对象实例时自动调用该方法。并且,该方法是类中必须有的,如果没有显示定义,则会默认添加空的constructor( )方法。

super( ) ---->继承

在class方法中,继承是使用 extends 关键字来实现的。子类 必须 在 constructor( )调用 super( )方法,否则新建实例时会报错。

将生命周期方法添加到 Class 中

在具有许多组件的应用程序中,当组件被销毁时释放所占用的资源是非常重要的。

Clock 组件第一次被渲染到 DOM 中的时候,就为其设置一个计时器。这在 React 中被称为“挂载(mount)”。

同时,当 DOM 中 Clock 组件被删除的时候,应该清除计时器。这在 React 中被称为“卸载(unmount)”。

创建一个tick方法,Clock组件每秒都会调用它。

我们可以为 class 组件声明一些特殊的方法,当组件挂载或卸载时就会去执行这些方法:

  componentDidMount() {
      this.timerID = setInterval(() => this.tick(),1000);
  }

  componentWillUnmount() {
      clearInterval(this.timeID);
  }

  tick() {
      this.setState({time: new Date()});
  }

调用顺序:

  1. <Clock /> 被传给 ReactDOM.render()的时候,React 会调用 Clock 组件的构造函数。因为 Clock 需要显示当前的时间,所以它会用一个包含当前时间的对象来初始化 this.state。我们会在之后更新 state。
  2. 之后 React 会调用组件的 render() 方法。这就是 React 确定该在页面上展示什么的方式。然后 React 更新 DOM 来匹配 Clock 渲染的输出。
  3. Clock 的输出被插入到 DOM 中后,React 就会调用 ComponentDidMount() 生命周期方法。在这个方法中,Clock 组件向浏览器请求设置一个计时器来每秒调用一次组件的 tick() 方法。
  4. 浏览器每秒都会调用一次 tick() 方法。 在这方法之中,Clock 组件会通过调用 setState() 来计划进行一次 UI 更新。得益于 setState() 的调用,React 能够知道 state 已经改变了,然后会重新调用 render() 方法来确定页面上该显示什么。这一次,render() 方法中的 this.state.date 就不一样了,如此以来就会渲染输出更新过的时间。React 也会相应的更新 DOM。
  5. 一旦 Clock 组件从 DOM 中被移除,React 就会调用 componentWillUnmount() 生命周期方法,这样计时器就停止了。

完整代码

class Clock extends React.Component {
  constructor(props) {
    super(props);
    this.state = {time: new Date()};
  }
  componentDidMount() {
    this.timerID = setInterval(() => this.tick(),2000);
  }
  componentWillUnmount() {
    clearInterval(this.timeID);
  }
  tick() {
    this.setState({time: new Date()});
  }
  render(){
          const element = (
            <div>
              <h1>Hello world!</h1>
              <h2>It is {this.state.time.toLocaleTimeString()}</h2>      
            </div>
          );
          return element;
    }
}
ReactDOM.render(<Clock />,document.getElementById('root'));

正确使用State

  1. 不要直接使用state

    将刚刚的代码中this.state = {time: new Date()};修改为this.state = new Date();将会渲染一次后报错

    Uncaught TypeError: this.state.toLocaleTimeString is not a function

  2. 更新异步

    出于性能考虑,React 可能会把多个 setState() 调用合并成一个调用。因为 this.propsthis.state 可能会异步更新,所以你不要依赖他们的值来更新下一个状态。

  3. 更新合并

    当你调用 setState() 的时候,React 会把你提供的对象合并到当前的 state。

事件处理

注意小驼峰命名

HTML中实现一个按钮的点击事件

<button onclick="activateLasers()">
  Activate Lasers
</button>

React中实现一个按钮的点击事件

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

可以用以下代码实现一个开关需求,代码如下:

class Button extends React.Component {
  constructor(props) {
    super(props);
    this.state = {isOFF: false};
    this.handleClick = this.handleClick.bind(this);
  }

  handleClick() {
    this.setState(aaa => ({
      isOFF: !aaa.isOFF
    }));
  }

  render() {
    return (
      <button onClick={this.handleClick}>
        {this.state.isOFF ? 'AA' : 'BB'}
      </button>
    );
  }
}

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

再该代码中this.handleClick = this.handleClick.bind(this);不可删除,这是因为在 JavaScript 中,class 的方法默认不会绑定 this。如果你忘记绑定 this.handleClick 并把它传入了 onClick,当你调用这个函数的时候 this 的值为 undefined

如果觉得写bind麻烦,可以在回调函数中使用箭头函数

class Button extends React.Component {
  constructor(props) {
    super(props);
    this.state = {isOFF: false};
    //this.handleClick = this.handleClick.bind(this);
  }

  handleClick() {
    this.setState(aaa => ({
      isOFF: !aaa.isOFF
    }));
  }

  render() {
    return (
      <button onClick={() => this.handleClick()}>
        {this.state.isOFF ? 'AA' : 'BB'}
      </button>
    );
  }
}

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

使用箭头函数在大多数情况下不会有问题,但如果该回调函数作为prop传入子组件时,这类组件可能回重新渲染。建议在构造器中绑定或使用 class fields 语法来避免这类性能问题。

事件传递参数

在循环中,通常我们会为事件处理函数传递额外的参数。例如,若 id 是你要删除那一行的 ID,以下两种方式都可以向事件处理函数传递参数:

<button onClick={(e) => this.deleteRow(id, e)}>Delete Row</button>
<button onClick={this.deleteRow.bind(this, id)}>Delete Row</button>

上述两种方式是等价的,分别通过箭头函数Function.prototype.bind 来实现。

在这两种情况下,React 的事件对象 e 会被作为第二个参数传递。如果通过箭头函数的方式,事件对象必须显式的进行传递,而通过 bind 的方式,事件对象以及更多的参数将会被隐式的进行传递。

条件渲染

在 React 中,你可以创建不同的组件来封装各种你需要的行为。然后,依据应用的不同状态,你可以只渲染对应状态下的部分内容。

React 中的条件渲染和 JavaScript 中的一样,使用 JavaScript 运算符 if 或者条件运算符去创建元素来表现当前的状态,然后让 React 根据它们来更新 UI。

function AdminPage(props){
  const element = <h1>Welcome, admin</h1>;
  return element;
}
function GuestPage(props){
  const element = <h1>Welcome, guest</h1>;
  return element;
}

function IsAdmin(props){
  if(props.admin){
    return <AdminPage />;
  }else{
    return <GuestPage />;
  }
}

ReactDOM.render(
  <IsAdmin admin={false} />,
  document.getElementById('root')
);

与运算符

使用花括号{}包裹代码,可以更加方便的进行元素渲染

function Mailbox(props) {
  const unreadMessages = props.unreadMessages;
  return (
    <div>
      <h1>Hello!</h1>
      {unreadMessages.length > 0 &&
        <h2>
          You have {unreadMessages.length} unread messages.
        </h2>
      }
    </div>
  );
}

const messages = ['React', 'Re: React', 'Re:Re: React'];
ReactDOM.render(
  <Mailbox unreadMessages={messages} />,
  document.getElementById('root')
);

三目运算符

另一种内联条件渲染的方法是使用 JavaScript 中的三目运算符 condition ? true : false

render() {
  const isLoggedIn = this.state.isLoggedIn;
  return (
    <div>
      The user is <b>{isLoggedIn ? 'currently' : 'not'}</b> logged in.
    </div>
  );
}

阻止组件渲染

在极少数情况下,你可能希望能隐藏组件,即使它已经被其他组件渲染。若要完成此操作,你可以让 render 方法直接返回 null,而不进行任何渲染。

function WarningBanner(props) {
  if (!props.warn) {
    return null;
  }

  return (
    <div className="warning">
      Warning!
    </div>
  );
}

class Page extends React.Component {
  constructor(props) {
    super(props);
    this.state = {showWarning: true}
    this.handleToggleClick = this.handleToggleClick.bind(this);
  }

  handleToggleClick() {
    this.setState(prevState => ({
      showWarning: !prevState.showWarning
    }));
  }

  render() {
    return (
      <div>
        <WarningBanner warn={this.state.showWarning} />
        <button onClick={this.handleToggleClick}>
          {this.state.showWarning ? 'Hide' : 'Show'}
        </button>
      </div>
    );
  }
}

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

在组件的 render 方法中返回 null 并不会影响组件的生命周期。

列表&&Key

(虽然我本人更想叫数组来着。)

const num_list = [1,2,3];
console.log(num_list);

渲染多个组件

根据数组生成数组元素平方的符号列表。

const a= [1,2,3];
const liA = a.map((a) => <li>{a*a}</li>);
ReactDOM.render(
  <ul>{liA}</ul>,
  document.getElementById('root')
);

或者这样

function NumSq(props){
  const numList = props.numList;
  const liA = numList.map((num) =><li>{num*num}</li>);
  return (<ul>{liA}</ul>);
}

const numList = [1,2,3]
ReactDOM.render(
  <NumSq numList={numList} />,
  document.getElementById('root')
);

运行这样的代码会出现一个WarningWarning: Each child in a list should have a unique "key" prop.

Key

key 帮助 React 识别哪些元素改变了,比如被添加或删除。因此你应当给数组中的每一个元素赋予一个确定的标识。

一个元素的 key 最好是这个元素在列表中拥有的一个独一无二的字符串。通常,我们使用数据中的 id 来作为元素的 key:

const todoItems = todos.map((todo) =>
  <li key={todo.id}>    {todo.text}
  </li>
);

当元素没有确定 id 的时候,万不得已你可以使用元素索引 index 作为 key:

const todoItems = todos.map((todo, index) =>
  // Only do this if items have no stable IDs  <li key={index}>    {todo.text}
  </li>
);

如果列表项目的顺序可能会变化,我们不建议使用索引来用作 key 值,因为这样做会导致性能变差,还可能引起组件状态的问题。可以看看 Robin Pokorny 的深度解析使用索引作为 key 的负面影响这一篇文章。如果你选择不指定显式的 key 值,那么 React 将默认使用索引用作为列表项目的 key 值。

Key提取组件

提取组件时注意,在map处设置即可。

错误示例:

function ListItem(props) {
  const value = props.value;
  return (
    // 错误!你不需要在这里指定 key:
    <li key={value.toString()}>
      {value}
    </li>
  );
}

function NumberList(props) {
  const numbers = props.numbers;
  const listItems = numbers.map((number) =>
    // 错误!元素的 key 应该在这里指定:
    <ListItem value={number} />
  );
  return (
    <ul>
      {listItems}
    </ul>
  );
}

const numbers = [1, 2, 3, 4, 5];
ReactDOM.render(
  <NumberList numbers={numbers} />,
  document.getElementById('root')
);

Key仅在兄弟节点唯一

当我们生成两个不同的数组时,我们可以使用相同的 key 值

function Blog(props) {
  const sidebar = (
    <ul>
      {props.posts.map((post) =>
        <li key={post.id}>
          {post.title}
        </li>
      )}
    </ul>
  );
  const content = props.posts.map((post) =>
    <div key={post.id}>
      <h1>{post.id}</h1>
      <h3>{post.title}</h3>
      <p>{post.content}</p>
    </div>
  );
  return (
    <div>
      {sidebar}
      <hr />
      {content}
    </div>
  );
}

const posts = [
  {id: 1, title: 'Hello World', content: 'Welcome to learning React!'},
  {id: 2, title: 'Installation', content: 'You can install React from npm.'}
];
ReactDOM.render(
  <Blog posts={posts} />,
  document.getElementById('root')
);

表单

HTML 表单元素的工作方式和其他的 DOM 元素有些不同,这是因为表单元素通常会保持一些内部的 state。

<form>
  <label>
    名字:
    <input type="text" name="name" />
  </label>
  <input type="submit" value="提交" />
</form>

此表单具有默认的 HTML 表单行为,即在用户提交表单后浏览到新页面。如果你在 React 中执行相同的代码,它依然有效。但大多数情况下,使用 JavaScript 函数可以很方便的处理表单的提交, 同时还可以访问用户填写的表单数据。实现这种效果的标准方式是使用“受控组件”。

受控组件

不知道为啥codepen么得弹框

class NameForm extends React.Component {
  constructor(props) {
    super(props);
    this.state = {value: ''};

    this.handleChange = this.handleChange.bind(this);
    this.handleSubmit = this.handleSubmit.bind(this);
  }

  handleChange(event) {
    this.setState({value: event.target.value});
  }

  handleSubmit(event) {
    document('A name was submitted: ' + this.state.value);
    event.preventDefault();
  }

  render() {
    return (
      <form onSubmit={this.handleSubmit}>
        <label>
          Name:
          <input type="text" value={this.state.value} onChange={this.handleChange} />
        </label>
        <input type="submit" value="Submit" />
      </form>
    );
  }
}

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

文件 input 标签

在 HTML 中,<input type="file"> 允许用户从存储设备中选择一个或多个文件,将其上传到服务器,或通过使用 JavaScript 的 File API 进行控制。

<input type="file" />

因为它的 value 只读,所以它是 React 中的一个非受控组件。


game to game, not win