React 入门实践教程

版本v16.12.0

本教程为用react从头开始实现一个小游戏。和另外的react基础知识是互补的

注:关于git命令行运行和直接cmd运行,一般情况下都用cmd命令行来运行,git它只会下载node相关的

一、环境准备

1、初始化项目

需要在你的机器上安装Node >= 8.10 和 npm >= 5.6

  • create-react-app脚手架创建项目: 这个create-react-app脚手架 就和vue-cli差不多的
    • 创建项目报错npm ERR! Unexpected end of JSON input while parsing near '...mist":"^1.2.0","mocha',解决办法为 清除npm 缓存npm cache clean --force; 还是不行的话 全局更新npm install npm -g
    • 报错This is probably not a problem with npm. There is likely additional logging output above.;解决方法位,删除依赖包rm -rf node_modules,删除锁定rm package-lock.json,删除缓存npm cache clear --force,最后npm install(这步也可以提到删除依赖包之前,不出错就不用删除这些了)
    • 报错cross-env' ▒▒▒▒▒ڲ▒▒▒▒ⲿ▒▒▒Ҳ。解决cnpm install -s cross-env,安装cross-env包,使用cross-env解决跨平台设置环境变量NODE_ENV的问题
    • 如果全局安装过create-react-app,则需要先卸载npm uninstall -g create-react-app
// 第一行的 npx 不是拼写错误 —— 它是 npm 5.2+ 附带的 package 运行工具
npx create-react-app my-app
cd my-app
npm start // 在package.json里面把start改成dev之后,依然可以用npm run dev开启服务器了
  • 创建包含TypeScript的create-react-app项目
    • 装好了之后,就可以重命名里面的js文件为tsx文件了,比如src/index.jssrc/index.tsx
    • 下面1为创建项目:初始化的react项目,直接就有一个TypeScript 的配置文件 tsconfig.json
    • 下面2为给项目安装声明文件:上面方法创建了项目之后,依然要安装声明文件
    • 不用typescript初始化的react项目,是没有TypeScript 的配置文件 tsconfig.json。安装配置文件输入后面命令npx tsc --init
// 1、初始化项目的时候,直接安装TypeScript
npx create-react-app my-app --typescript

// 2、安装声明文件
npm install --save typescript @types/node @types/react @types/react-dom @types/jest
  • 弹出webpack配置文件:创建了项目之后,默认webpack的配置被隐藏在node_moudles。如果要把这些配置释放出来(弄成vue的那种结构),需要使用命令npm run eject,这个弹出操作是不可逆的
    • 然后就可以配置端口号等webpack相关信息,并可在scripts/start下修改端口号
    • 报错This git repository has untracked files or uncommitted changes:。原因:因为我们使用脚手架创建一个项目的时候,自动给我们增加了一个.gitignore文件,而我们本地却没有文件仓库,所以就需要,先git init, 再git add . , 最后git commit -m "go"

2、初始化项目中的文件

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

二、React基础

1、react简介

React 是一个声明式,高效且灵活的用于构建用户界面的 JavaScript 库。使用 React 可以将一些简短、独立的代码片段组合成复杂的 UI 界面,这些代码片段被称作“组件”

2、React.Component 的子类

这个就相当于是封装一个组件了

// Square组件
class Square extends React.Component<any,any> {
    render() {
      return (
        <button className="square">
          {/* TODO */}
        </button>
      );
    }
  }
 
 // ShoppingList组件
class ShoppingList extends React.Component<any,any> {
  renderSquare(i: any) {
      return <Square value={i} />;
  }
  render() {
  const status = 'Next player: X';
    return (
      <div className="shopping-list">
        <h1>Shopping List for {this.props.name}</h1>
        <ul>
          <li>Instagram {status}</li>
          <li>
            {this.renderSquare(0)}
            {this.renderSquare(1)}
            {this.renderSquare(2)}
          </li>
          <li>Oculus</li>
        </ul>
      </div>
    );
  }
}

// 用法示例: <ShoppingList name="Mark" />
  • 上面代码解析
    • class组件:简单说继承React.Component 的子类就是一个react组件,或者说是一个class组件。比如,上面的ShoppingList 是一个 React 组件类,简单说就是一个react的组件;除去class组件之外还有函数组件。
      • render()函数:render函数的返回值为显示在页面上的DOM树,这个就是个虚拟dom。在react中,称返回的为一个React 元素
      • 父组件给子组件参数传递props:父组件给子组件传递参数依然是用 props(“props” 是 “properties” 简写);然后传递进来的参数在render方法里面,通过this.props来调用。this.props上面的属性名字,就为传进来的名字,比如上面的this.props.name
    • jsx: 上述代码的render的返回值,实际上就是用了JSX语法,就是直接写dom结构就好了。如果不用jsx语法,则需要使用React.createElement('div')这种来一个一个的创建,当然jsx语法最后依然会被编译成后面那种。
      • jsx中使用JavaScript 表达式,只需要用一个大括号把表达式括起来

3、阅读index.tsx的初始代码

1、父组件给子组件通过 Props 传递数据

向一个组件传值:在 Board 组件的 renderSquare 方法中,我们将代码改写成下面这样,传递一个名为 value 的 prop 到 Square 当中

class Board extends React.Component<any,any>{
  renderSquare(i: any) {
    return <Square value={i} />;
  }
}

使用传过来的值:修改 Square 组件中的 render 方法,把 {/* TODO */} 替换为 {this.props.value},以显示上文中传入的值

class Square extends React.Component<any,any> {
  render() {
    return (
      <button className="square">
        {this.props.value}
      </button>
    );
  }
}

2、给组件添加交互功能

添加事件处理函数:可以直接用onClick来绑定在标签上面。修改Square 组件中 render() 方法的返回值中的 button 标签

class Square extends React.Component<any,any> {
    render() {
      return (
        <button className="square" onClick={() => alert(this.props.value)}>
          {this.props.value}
        </button>
      );
    }
  }
  • 组件的state
    • 组件内部数据state: 每个组件中可以有state,用来存当前组件的数据,且应该被视为一个组件的私有属性。在组件的构造函数中初始化,其余地方用this.state来访问
    • this.setState(obj): 这个为修改组件中的state,this.setState(),传入的值为要修改的属性的一个对象
    • constructor: 每次调用这个组件的时候,都会先调用这个组件的constructor方法。初始化state就必须添加一个constructor。每次定义子类的构造函数时,都需要调用 super 方法,所以在所有含有构造函数的的 React 组件中,构造函数必须以 super(props) 开头。
class Square extends React.Component<any,any> {
  constructor(props: any) {
    super(props);
    this.state = {value2: props.value};
  }
    render() {
      return (
        <button className="square" onClick={() => this.setState({value2: 'x'})}>
          {this.state.value2}
        </button>
      );
    }
  }
  • React Developer Tools拓展: 在 Chrome 或者 Firefox 中安装扩展 React Devtools 可以让你在浏览器开发者工具中查看 React 的组件树。https://chrome.google.com/webstore/detail/react-developer-tools/fmkadmapgofadopljbjfkapdkoienihi?hl=en
    • 可以用来检查 React 组件的 state 和 props,还有查看react的组件树
    • 可以来判断处于哪个版本:如果处于 开发模式的网站,图标背景会变成红色;如果处于 生产版本的网站,图标背景会变成深色;

三、React常用技术

1、子组件state提升

在react中,当你遇到需要同时获取多个子组件数据,或者两个组件之间需要相互通讯的情况时,需要把子组件的 state 数据提升至其共同的父组件当中保存。之后父组件可以通过 props 将状态数据传递到子组件当中

  • 子组件给父组件传递参数:在react中,也是通过自定义事件来使子组件给父组件传递参数。不过好像是不能用$emit这种来触发父组件中的自定义事件的。react中传参如下:
    • 父元素中:首先一样是在使用子组件的时候绑定一个自定义事件,事件里面绑定一个事件处理函数。在 React中,有一个命名规范,通常会将自定义事件命名为 on[Event],将事件处理函数命名为 handle[Event] 这样的格式
    • 子元素中:子元素中直接在this.props上面调用自定义事件就好了,而不是用this.$emit触发了
    • 注:由于在react中,父组件调用子组件的时候,写在子组件标签上面的属性都会放在子组件的props中,所以在子组件触发自定义事件的时候,就相当于是父组件调用了那个函数(因为函数调用是调用的定义的那个位置的),所以就可以向父组件传值了
// 子组件
class Square extends React.Component<any,any> {
    render() {
      return (
        <button className="square" onClick={() => this.props.onSquareClick(this.props.index)}> // 子组件上面调用自定义事件,并且传值
          {this.props.value}
        </button>
      );
    }
  }

// 父组件
  class Board extends React.Component<any,any> {
    constructor(props: any) {
      super(props);
      this.state = {
        squares: [
        'O', null, 'X',
        'X', 'X', 'O',
        'O', null, null,
      ]};
    }
    handleSquareClick(index: any) { // 父组件中定义的自定义事件的处理函数
      let squares = this.state.squares.slice(0);
      squares[index] = 'X';
      this.setState({squares:squares});
      console.log(index);
    }
    renderSquare(i: any) {
      return <Square index={i} value={this.state.squares[i]} onSquareClick={(index:any) => this.handleSquareClick(index)}/>; // 父组件写给子组件的props
    }
  
    render() {
      const status = 'Next player: X';
      return (
        <div>
          <div className="status">{status}</div>
          <div className="board-row">
            {this.renderSquare(0)}
            {this.renderSquare(1)}
            {this.renderSquare(2)}
          </div>
        </div>
      );
    }
  }

2、React 中的不可变性

一般情况下,不直接修改数据,而是存一个变量,然后来修改。比如上面的let squares = this.state.squares.slice(0);,就是把数组存了一份,然后改的,没有直接改state。不可变性最主要的优势在于它可以帮助我们在 React 中创建 pure components。我们可以很轻松的确定不可变数据是否发生了改变,从而确定何时对组件进行重新渲染

3、函数组件

函数组件是相对于class组件来说的。使用函数组件的时候报错了。

  • 函数组件不需要定义一个继承于 React.Component的类,我们可以定义一个函数,这个函数只包含一个 render 方法,接收 props作为参数,然后返回需要渲染的元素,并且不包含 state
// 下面函数替换Square 类组件
function Square(props: any) {
    return (
      <button className="square" onClick={props.onSquareClick(props.index)}>
        {props.value}
      </button>
    );
  }

4、添加游戏的状态

如下,就是添加了一个下一步状态。

render的时候调用函数,直接把函数放在render里面。要想不再render,只需要不在改变state就好了,具体到这里,就是直接返回handleSquareClick

  class Board extends React.Component<any,any> {
    constructor(props: any) {
      super(props);
      this.state = {
        squares: [
        'O', null, 'X',
        'X', 'X', 'O',
        'O', null, null,
      ],
      xIsNext: true}; // 这里多添加了一个状态
    }
    handleSquareClick(index: any) {
      let squares = this.state.squares.slice(0);
      if (calculateWinner(squares) || squares[i]) {
      return; // 直接退出,不修改state了,就不会执行后面的render了
    }
      squares[index] = this.state.xIsNext ? 'X' : 'O'; // 这里做了一个判断
      this.setState({squares:squares,xIsNext: !this.state.xIsNext,});
      console.log(index);
    }
    renderSquare(i: any) {
      return <Square index={i} value={this.state.squares[i]} onSquareClick={(index:any) => this.handleSquareClick(index)}/>;
    }
  
    render() {
      const winner = calculateWinner(this.state.squares); // render(dom加载)的时候调用这个函数
    let status;
    if (winner) {
      status = 'Winner: ' + winner;
    } else {
      status = 'Next player: ' + (this.state.xIsNext ? 'X' : 'O');
    }
     }
function calculateWinner(squares: any) {}

四、React优势

重新加载组件,依然是用key或者ref来控制(又好像不是。有可能key只是与性能有关)。key是React中的一个特殊的保留属性(以及ref,还有一个更高级的功能)。创建元素时,React提取key属性并将键直接存储在返回的元素上。即使key看起来像它所属props,key也无法使用引用this.props.key。React自动用于key确定要更新的组件

    const moves = history.map((step, move) => {
      const desc = move ?
        'Go to move #' + move :
        'Go to game start';
      return (
        <li key={move}>
          <button onClick={() => this.jumpTo(move)}>{desc}</button>
        </li>
      );
    });

猜你喜欢

转载自blog.csdn.net/rocktanga/article/details/121329721