React

2022-11-14 20:12:21 浏览数 (1)

来点前端

1. 介绍

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

空模板展示

代码语言:javascript复制
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <title>Hello World</title>
    <script src="https://unpkg.com/react@18/umd/react.development.js"></script>
    <script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
    <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
  </head>
  <body>
    <div id="root"></div>

      
      
    <!-- 开始写脚本 -->
    <script type="text/babel">
    
    // React 将替换 DOM 容器中的任何现有内容,所以建议为空
    const root = ReactDOM.createRoot(document.getElementById('root'));
    root.render(<h1>Hello, world!</h1>);

    </script>
    <!-- 结束写脚本 -->
  </body>
</html>

2. JSX

被称为 JSX,它是 JavaScript 的语法扩展,JSX 产生 React “元素”,可渲染到 DOM 中

代码语言:javascript复制
const element = <h1>Hello, world!</h1>;

const element = (
  <div>
    <h1>Hello!</h1>
    <h2>Good to see you here.</h2>
  </div>
);

JSX 中嵌入表达式

任何有效的 JavaScript 表达式 放在 JSX 中的花括号内解析

代码语言:javascript复制
const name = 'Josh Perez';
const element = <h1>Hello, {name}</h1>;

JSX 也是一个表达式,编译后成为常规的 javascript 函数调用并计算为 javascript 对象,意味着可在 if、for 中使用,将其分配给变量,或作为参数接受,或函数中返回

代码语言:javascript复制
function getGreeting(user) {
  if (user) {
    return <h1>Hello, {formatName(user)}!</h1>;
  }
  return <h1>Hello, Stranger.</h1>;
}

JSX 是一个语法糖,Babel 将 JSX 转成 React.createElement() 产生 React element,React 读取这个对象来构造 DOM

代码语言:javascript复制
// 语法糖
const element = (
  <h1 className="greeting">
    Hello, world!
  </h1>
);

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

// React 对象
const element = {
  type: 'h1',
  props: {
    className: 'greeting',
    children: 'Hello, world!'
  }
};

2. 渲染元素

React element 描述了在屏幕上展示的内容

代码语言:javascript复制
const element = <h1>Hello, world</h1>;

React DOM 负责更新浏览器 DOM 以匹配 React 元素

渲染一个 React 元素,首先要将 DOM 元素传递给 ReactDOM.createRoot() 创建出 React DOM 元素(root),然后再将 React 元素传递给 root.render()

代码语言:javascript复制
const root = ReactDOM.createRoot(document.getElementById('root'));
const element = <h1>Hello, world</h1>;
root.render(element);

React 元素是不可变的,一旦你创建了一个元素,你就不能改变它的子元素或属性。目前更新的方法是创建新元素并传递给 root.render()

代码语言:javascript复制
const root = ReactDOM.createRoot(
  document.getElementById('root')
);

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

setInterval(tick, 1000);

React 只会更新必要的部分,React DOM 比较元素及其子元素内容先后的不同,而在渲染过程中只会更新改变了的部分

3. 组件

组件在概念上类似于 JavaScript 函数,它接受任意的入参(即 “props”),并返回 React 元素用于描述页面展示内容

代码语言:javascript复制
// DOM 标签(非组件)
const element = <div />;

// 下面二者是等效的
// 函数组件
function Welcome(props) {
  return <h1>Hello, {props.name}</h1>;
}

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

// 标签组件,自定义标签为组件
function Welcome(props) {
  return <h1>Hello, {props.name}</h1>;
}
const element = <Welcome name="Sara" />;
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(element);

组件名为大写,JSX 属性为小写驼峰

组合组件

代码语言:javascript复制
function Welcome(props) {
  return <h1>Hello, {props.name}</h1>;
}

function App() {
  return (
    <div>
      <Welcome name="Sara" />
      <Welcome name="Cahal" />
      <Welcome name="Edite" />
    </div>
  );
}

函数也作为标签使用

props 的只读性,所有 React 组件都必须像纯函数一样保护它们的 props 不被更改,提供了 state 来动态变化

代码语言:javascript复制
function sum(a, b) {
  return a   b;
}

function change(a, b) {
    a = b;
    return a   b;
}

4. state & 生命周期

React 元素是不可变的,但我们希望只编写一次代码就可以实现组件自动更新,需要借助 state 来实现(State 与 props 类似,但是 state 是私有的,并且完全受控于当前组件,state 变更会调用 render方法),首要要先函数组件转成类组件:

  1. 创建一个同名的 ES6 class,并且继承于 React.Component
  2. 添加一个空的 render() 方法
  3. 将函数体移动到 render() 方法之中
  4. render() 方法中使用 this.props 替换 props
  5. 删除剩余的空函数声明
代码语言:javascript复制
class Clock extends React.Component {
  render() {
    return (
      <div>
        <h1>Hello, world!</h1>
        <h2>It is {this.props.date.toLocaleTimeString()}.</h2>
      </div>
    );
  }
}

每次组件更新时 render 方法都会被调用

生命周期方法

代码语言:javascript复制
class Clock extends React.Component {
  constructor(props) {
    super(props);
    this.state = {date: new Date()};
  }

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

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

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

  render() {
    return (
      <div>
        <h1>Hello, world!</h1>
        <h2>It is {this.state.date.toLocaleTimeString()}.</h2>
      </div>
    );
  }
}

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

不要直接修改 state:

代码语言:javascript复制
// Wrong
this.state.comment = 'Hello';

// Correct
// 应该使用 setState():
this.setState({comment: 'Hello'});

State 的更新可能是异步的,可能会把多个 setState() 调用合并成一个调用

代码语言:javascript复制
// Wrong
// 多个setState调用会合并,结果可能不正确
this.setState({
  counter: this.state.counter   this.props.increment,
});


// Correct
// 改成函数即可
this.setState((state, props) => ({
  counter: state.counter   props.increment
}));

state的更新合并

代码语言:javascript复制
// 里面有 posts、comments
constructor(props) {
    super(props);
    this.state = {
        posts: [],
        comments: []
    };
}

// 只会更新 comments、posts还是存在的 
this.setState({comments}

5. 事件处理

React 元素的事件需要传入的一个字符串函数解析,而传统的 html 是传入一个方法调用

代码语言:javascript复制
<button onclick="activateLasers()">
  Activate Lasers
</button>

// 
<button onClick={activateLasers}>
  Activate Lasers
</button>
代码语言:javascript复制
class Toggle extends React.Component {
    constructor(props) {
        super(props);
        this.state = {isToggleOn: true};

        // 为了在回调中使用 `this`,这个绑定是必不可少的
        this.handleClick = this.handleClick.bind(this);
  	}
    
    handleClick() {
        // xxx
    }
      
  	render() {
    	return (
            // 这里的this.handleClick  或者 handleClick()
            // 但 react 用前者,所以得绑定
      		<button onClick={this.handleClick}></button>
    	);
  	}
}

小写驼峰命名法 class 的方法不会绑定 this,没有绑定而传入了 onClick,调用时 this 为 undefined

向事件处理函数传递参数

代码语言:javascript复制
// 显隐式的传递 e 事件
// this 的 react 方式隐式传递了 event 
<button onClick={(e) => this.deleteRow(id, e)}>Delete Row</button>
<button onClick={this.deleteRow.bind(this, id)}>Delete Row</button>

6. 条件渲染

if 语句

代码语言:javascript复制
function UserGreeting(props) {
  return <h1>Welcome back!</h1>;
}

function GuestGreeting(props) {
  return <h1>Please sign up.</h1>;
}

function Greeting(props) {
  const isLoggedIn = props.isLoggedIn;
  if (isLoggedIn) {
    return <UserGreeting />;
  }
  return <GuestGreeting />;
}

const root = ReactDOM.createRoot(document.getElementById('root')); 

// 直接传参
root.render(<Greeting isLoggedIn={false} />);

元素变量判断

代码语言:javascript复制
render() {
    const isLoggedIn = this.state.isLoggedIn;
    let button;
    if (isLoggedIn) {
        button = <LogoutButton onClick={this.handleLogoutClick} />;
    } else {
        button = <LoginButton onClick={this.handleLoginClick} />;
    }

    return (
        <div>
        	{button}
        </div>
    );
}

&& 内联判断

代码语言:javascript复制
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'];

const root = ReactDOM.createRoot(document.getElementById('root')); 
root.render(<Mailbox unreadMessages={messages} />);

JavaScript 中:true && expression 返回 expression, false && expression 返回 false

三目运算符

代码语言:javascript复制
render() {
  const isLoggedIn = this.state.isLoggedIn;
  return (
    <div>
      {isLoggedIn
        ? <LogoutButton onClick={this.handleLogoutClick} />
        : <LoginButton onClick={this.handleLoginClick} />
      }
    </div>
  );
}

阻止组件渲染

代码语言:javascript复制
function WarningBanner(props) {
  if (!props.warn) {
    return null;
  }

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

返回 null,不会影响生命周期,componentDidUpdate 还会继续执行

7. list & key

数组转为元素列表

代码语言:javascript复制
// 使用 {} 在 JSX 内构建一个元素集合
const numbers = [1, 2, 3, 4, 5];
    
function NumberList(props) {
    const numbers = props.numbers;
    const listItems = numbers.map((item) =>
    	<li key={number.toString()>		// 为每个列表元素分配一个key
          {item}						// 解析
    	</li>
    );
    return (
        <ul>{listItems}</ul>			// 解析-构建元素集合
    );
}

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<NumberList numbers={numbers} />);

a key should be provided for list items(创建一个元素时必须包括一个特殊的 key 属性) key 帮助 React 识别哪些元素改变了,比如被添加或删除,因此要为数组中的每一个元素赋予一个确定的标识

列表的 key

代码语言:javascript复制
// key 是在该列表中唯一标识,通常选择数据的id,当没有时可以使用index代替
const todoItems = todos.map((todo) =>
  <li key={todo.id}>
    {todo.text}
  </li>
);

// 万不得已才使用
// 列表顺序可能变化了,这将导致错误状态
const todoItems = todos.map((todo, index) =>
  <li key={index}>
    {todo.text}
  </li>
);

key 会传递信息给 React ,但不会传递给你的组件。如果你的组件中需要使用 key 属性的值,请用其他属性名显式传递这个值

8. 表单

受控组件

代码语言:javascript复制
class NameForm extends React.Component {
  constructor(props) {
    super(props);
    this.state = {value: ''};

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

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

  // 阻止跳转
  handleSubmit(event) {
    alert('提交的名字: '   this.state.value);
    event.preventDefault();
  }

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

textarea 标签

代码语言:javascript复制
// 原本 <textarea> 元素通过其子元素定义其文本
// 而 react 中可使用 value 属性
class EssayForm extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      value: '请撰写一篇关于你喜欢的 DOM 元素的文章.'
    };

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

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

  handleSubmit(event) {
    alert('提交的文章: '   this.state.value);
    event.preventDefault();
  }

  render() {
    return (
      <form onSubmit={this.handleSubmit}>
        <label>
          文章:
          <textarea value={this.state.value} onChange={this.handleChange} />
        </label>
        <input type="submit" value="提交" />
      </form>
    );
  }
}

select 标签

代码语言:javascript复制
// 传统的在 option 里面有个 selected 属性
<select>
  <option value="grapefruit">葡萄柚</option>
  <option value="lime">酸橙</option>
  <option selected value="coconut">椰子</option>
  <option value="mango">芒果</option>
</select>


// 而 React 在根标签 select 上使用 value 属性
class FlavorForm extends React.Component {
  constructor(props) {
    super(props);
    this.state = {value: 'coconut'};

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

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

  handleSubmit(event) {
    alert('你喜欢的风味是: '   this.state.value);
    event.preventDefault();
  }

  render() {
    return (
      <form onSubmit={this.handleSubmit}>
        <label>
          选择你喜欢的风味:
          <select value={this.state.value} onChange={this.handleChange}>
            <option value="grapefruit">葡萄柚</option>
            <option value="lime">酸橙</option>
            <option value="coconut">椰子</option>
            <option value="mango">芒果</option>
          </select>
        </label>
        <input type="submit" value="提交" />
      </form>
    );
  }
}

9. 状态提升

两个组件需要数据同步时,将 state 提升到父组件(此时调用将变成 this.props.attr),因为父组件会将参数 props 传递给子组件。又因为 state 是私有的,且提升后属于父组件,不受子组件控制,此时子组件想要改变父组件的 state 只能依靠 父组件将 setState 方法包装成函数通过 props 传递给子组件调用

代码语言:javascript复制
class TemperatureInput extends React.Component {
  constructor(props) {
    super(props);
    this.handleChange = this.handleChange.bind(this);
  }

  handleChange(e) {
    this.props.onTemperatureChange(e.target.value);
  }

  render() {
    const temperature = this.props.temperature;
    const scale = this.props.scale;
    return (
      <fieldset>
        <legend>Enter temperature in {scaleNames[scale]}:</legend>
        <input value={temperature}
               onChange={this.handleChange} />
      </fieldset>
    );
  }
}
代码语言:javascript复制
class Calculator extends React.Component {
  constructor(props) {
    super(props);
    this.handleCelsiusChange = this.handleCelsiusChange.bind(this);
    this.handleFahrenheitChange = this.handleFahrenheitChange.bind(this);
    this.state = {temperature: '', scale: 'c'};
  }

  handleCelsiusChange(temperature) {
    this.setState({scale: 'c', temperature});
  }

  handleFahrenheitChange(temperature) {
    this.setState({scale: 'f', temperature});
  }

  render() {
    const scale = this.state.scale;
    const temperature = this.state.temperature;
    const celsius = scale === 'f' ? tryConvert(temperature, toCelsius) : temperature;
    const fahrenheit = scale === 'c' ? tryConvert(temperature, toFahrenheit) : temperature;

    return (
      <div>
        <TemperatureInput
          scale="c"
          temperature={celsius}
          onTemperatureChange={this.handleCelsiusChange} />
        <TemperatureInput
          scale="f"
          temperature={fahrenheit}
          onTemperatureChange={this.handleFahrenheitChange} />
        <BoilingVerdict
          celsius={parseFloat(celsius)} />
      </div>
    );
  }
}

10. 组合关系

包含关系

代码语言:javascript复制
// 展示的是
// Welcome
// Thank you for visiting our spacecraft!
function FancyBorder(props) {
  return (
    <div className={'FancyBorder FancyBorder-'   props.color}>
      {props.children}
    </div>
  );
}


function WelcomeDialog() {
  return (
    <FancyBorder color="blue">
      <h1 className="Dialog-title">
        Welcome
      </h1>
      <p className="Dialog-message">
        Thank you for visiting our spacecraft!
      </p>
    </FancyBorder>
  );
}

porps.children 也是一个保留字段,里面有该标签中的所有内容(包括属性、子元素、文本)

也可不使用 children 属性

代码语言:javascript复制
function SplitPane(props) {
  return (
    <div className="SplitPane">
      <div className="SplitPane-left">
        {props.left}
      </div>
      <div className="SplitPane-right">
        {props.right}
      </div>
    </div>
  );
}

function App() {
  return (
    <SplitPane
      left={
        <Contacts />
      }
      right={
        <Chat />
      } />
  );
}

传递一个元素进去也是可以的

特别关系

代码语言:javascript复制
// 一些组件看作是其他组件的特殊实例,比如 WelcomeDialog 可以说是 Dialog 的特殊实例
function Dialog(props) {
  return (
    <FancyBorder color="blue">
      <h1 className="Dialog-title">
        {props.title}
      </h1>
      <p className="Dialog-message">
        {props.message}
      </p>
    </FancyBorder>
  );
}

function WelcomeDialog() {
  return (
    <Dialog
      title="Welcome"
      message="Thank you for visiting our spacecraft!" />
  );
}

0 人点赞