React: 基礎概述

什麼是React

React是一個聲明式,高效且靈活的用於構建用戶界面的JavaScript 庫。使用 React 可以將一些簡短、獨立的代碼片段組合成複雜的 UI 界面,這些代碼片段被稱作組件

搭建本地開發環境

  • 安裝Node.js
  • 創建一個新項目

npx create-react-app sample-app

更新信息參考創建新的 React 應用

JSX

JSX是一個 JavaScript 的語法擴展,可以生成 React “元素”。其格式比較像是模版語言,但事實上完全是在JavaScript內部實現的。(元素是構成React應用的最小單位,JSX就是用來聲明React當中的元素,React使用JSX來描述用戶界面)

JSX的本質不是模板語言,而是動態創建組件的一種語法糖,讓你在JavaScript代碼中直接編寫HTML

以下兩種代碼是等效的

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

約定

  • 小寫的tag是原生的DOM節點
  • 大寫字母開頭的爲自定義組件
  • JSX標記可以直接使用屬性語法

在JSX中使用表達式

在 JSX 語法中,你可以在大括號內放置任何有效的 JavaScript 表達式。

  • JSX本身也是表達式
const element = <h1>Hello, world!</h1>;
  • 在屬性中使用表達式
<MyComponent> foo={1+2+3}</MyComponent>
  • 延展屬性
const user = {
  firstName: "淼",
  lastName: "汪"
};
const greeting = <Greeting {...props} />;
  • 表達式作爲子元素
const element = <h1>{user.name}</h1>;

元素渲染

元素React應用程序的最小構件。 與瀏覽器的 DOM 元素不同,React 元素是創建開銷極小的普通對象。React DOM 會負責更新 DOM 來與 React 元素保持一致。

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

// 在 setInterval() 回調函數,每秒都調用 ReactDOM.render()。
setInterval(() => {
  tick();
}, 1000);

組件和Props

組件允許你將UI分割成獨立的、可重用的部分,並獨立地考慮每個部分。

函數組件

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

class組件

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

渲染組件

使用ReactDOM.render()渲染組件

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

const element = <Welcome name="Sara" />;
ReactDOM.render(
  element,
  document.getElementById('root')
);

生命週期和State

生命週期

在這裏插入圖片描述
來自:http://projects.wojtekmaj.pl/react-lifecycle-methods-diagram/

示例

先上一個時鐘的代碼片段

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>
        <h2>現在是 {this.state.date.toLocaleTimeString()}</h2>
      </div>
    );
  }
}
ReactDOM.render(<Clock></Clock>, document.getElementById("root"));

解釋:
先說下State,State 與 props 類似,但是 state 是私有的,並且完全受控於當前組件。直接使用this.state.comment = ‘Hello’;是無法修改state的(除非是在構造函數中,構造函數時唯一可以給this.state賦值的地方),但是你可以在通過this.setState()來更新state。
componentDidMount()方法(掛載)在組件已經被渲染到DOM中後運行,所以我們在此處設置計時器。
componentWillUnmount()方法(卸載)中,我們執行清除計時器操作。

數據是向下流動的

不管是父組件或是子組件都無法知道某個組件是有狀態的還是無狀態的,並且它們也並不關心它是函數組件還是 class 組件。
這就是爲什麼稱 state 爲局部的或是封裝的的原因。除了擁有並設置了它的組件,其他組件都無法訪問。
組件可以選擇把它的 state 作爲 props 向下傳遞到它的子組件中:

事件處理

  • React 事件的命名採用小駝峯式(camelCase),而不是DOM元素中的純小寫。
  • 使用 JSX 語法時需要傳入一個函數作爲事件處理函數,而不是一個字符串。
    在React中,事件處理方式如下:
<button onClick={activateLasers}>
  Activate Lasers
</button>

在React 中另一個不同點是你不能通過返回 false 的方式阻止默認行爲。你必須顯式的使用 preventDefault,若要阻止默認行爲,參照如下:

function ActionLink() {
  function handleClick(e) {
    e.preventDefault();
    console.log('The link was clicked.');
  }
  return (
    <a href="#" onClick={handleClick}>
      Click me
    </a>
  );
}

示例

class Toggle extends React.Component {
  constructor(props) {
    super(props);
    this.state = { isToggleOn: true };
    // 爲了在回調中使用`this`,這個綁定時必須的
    this.handleClick = this.handleClick.bind(this);
  }
  handleClick() {
    this.setState(state => ({
      isToggleOn: !state.isToggleOn
    }));
  }
  render() {
    return (
      <button onClick={this.handleClick}>
        {this.state.isToggleOn ? "開" : "關"}
      </button>
    );
  }
}
ReactDOM.render(<Toggle></Toggle>, document.getElementById("root"));

列表和Key

基礎列表組件

function NumberList(props) {
  const numbers = props.numbers;
  const listItems = numbers.map((number) =>
    <li>
      {number}
    </li>
  );
  return (
    <ul>{listItems}</ul>
  );
}
const numbers = [1, 2, 3, 4, 5];
ReactDOM.render(
  <NumberList numbers={numbers} />,
  document.getElementById('root')
);

在運行以上代碼後,有一個警告信息:

Each child in a list should have a unique ‘key’

我們可以通過<li key={number.toString()}>設置key

Key

key 幫助 React 識別哪些元素改變了,比如被添加或刪除。因此你應當給數組中的每一個元素賦予一個確定的標識。

數組元素中使用的 key 在其兄弟節點之間應該是獨一無二的。然而,它們不需要是全局唯一的。當我們生成兩個不同的數組時,我們可以使用相同的 key 值。

元素的 key 只有放在就近的數組上下文中才有意義。我們修改下這個列表組件

function ListItem(props) {
  return <li>{props.value}</li>;
}
function NumberList(props) {
  const numbers = props.numbers;
  const listItems = numbers.map((number) =>
    <ListItem key={number.toString()} value={number} />
  );
  return (
    <ul>
      {listItems}
    </ul>
  );
}
const numbers = [1, 2, 3, 4, 5];
ReactDOM.render(
  <NumberList numbers={numbers} />,
  document.getElementById('root')
);

表單

textarea 標籤

class EasyForm 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) {
    alert("提交的文章:" + this.state.value);
    event.preventDefault();
  }
  render() {
    return (
      <form onSubmit={this.handleSubmit}>
        <label>
          內容:
          <textarea
            value={this.state.value}
            onChange={this.handleChange}
          ></textarea>
        </label>
        <input type="submit" value="提交"></input>
      </form>
    );
  }
}

select 標籤

class FruitForm extends React.Component {
  constructor(props) {
    super(props);
    this.state = { value: "strawberry" };

    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="apple">蘋果</option>
            <option value="watermelon">西瓜</option>
            <option value="strawberry">草莓</option>
          </select>
        </label>
        <input type="submit" value="提交"></input>
      </form>
    );
  }
}

處理多個輸入

class Reservation extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      isGoing: true,
      numberOfGuests: 2
    };
    this.handleInputChange = this.handleInputChange.bind(this);
  }

  handleInputChange(event) {
    const target = event.target;
    const value = target.name === "isGoing" ? target.checked : target.value;
    const name = target.name;

    this.setState({
      [name]: value
    });
  }

  render() {
    return (
      <form>
        <label>
          參與:
          <input
            name="isGoing"
            type="checkbox"
            checked={this.state.isGoing}
            onChange={this.handleInputChange}
          ></input>
        </label>
        <br />
        <label>
          來賓人數:
          <input
            name="numberOfGuests"
            type="number"
            value={this.state.numberOfGuests}
            onChange={this.handleInputChange}
          ></input>
        </label>
      </form>
    );
  }
}

狀態提升

狀態提示是之我們需要多個組件需要反映相同的變化數據時,將共享狀態提升到最近的公共父組件中。
我們在以下示例中描述了在指定溫度(華氏度和攝氏度)下,水是否沸騰的案例。其中當華氏度(或攝氏度)中的一方值發生變化時,另一方應當自動計算。

function BoilingVerdict(props) {
  if (props.celsius >= 100) {
    return <p>水沸騰了</p>;
  } else {
    return <p>水還沒有沸騰</p>;
  }
}
const scaleNames = {
  c: "攝氏度",
  f: "華氏度"
};
class TemperatureInput extends React.Component {
  constructor(props) {
    super(props);
    this.handleChange = this.handleChange.bind(this);
    // this.state = { temperature: "" };
  }
  handleChange(e) {
    // this.setState({ temperature: e.target.value });
    this.props.onTemperatureChange(e.target.value);
  }

  render() {
    const temperature = this.props.temperature;
    const scale = this.props.scale;
    return (
      <fieldset>
        <legend>輸入溫度({scaleNames[scale]}</legend>
        <input value={temperature} onChange={this.handleChange}></input>
      </fieldset>
    );
  }
}

function toCelsius(fahrenheit) {
  return ((fahrenheit - 32) * 5) / 9;
}
function toFahrenheit(celsius) {
  return (celsius * 9) / 5 + 32;
}

function tryConvert(temperature, convert) {
  const input = parseFloat(temperature);
  if (Number.isNaN(input)) {
    return "";
  }
  const output = convert(input);
  const rounded = Math.round(output * 1000) / 1000;
  return rounded.toString();
}

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>
        <TemperatureInput
          scale="f"
          temperature={fahrenheit}
          onTemperatureChange={this.handleFahrenheitChange}
        ></TemperatureInput>
        <BoilingVerdict celsius={parseFloat(temperature)}></BoilingVerdict>
      </div>
    );
  }
}

ReactDOM.render(<Calculator></Calculator>, document.getElementById("root"));

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