1

最近调部门,改用React开发了,整理一些常用知识点。

更多内容还请参考:
React 中文文档
Ant Design

1. 项目创建

# 创建项目
npx create-react-app your-project-name

# antd
npm i antd

# 路由
npm i react-router-dom

2. 启动服务

npm start
Vue 脚手架构建的项目使用 npm run serve

3. 安装插件

VS Code安装ES 7,非常方便生成组件代码

创建 Login.js 文件

输入 rfce 按回车,就可以初始化一个组件代码

import React from 'react'

function Login() {
    return (
        <div>
        
        </div>
    )
}

export default Login

4. 创建路由

1)简单实现

打开根目录中的 index.js 文件,引入路由模块,添加如下代码:

import React from 'react';
import ReactDOM from 'react-dom';
// 引入路由模块,使用哈希路由
import { HashRouter as Router, Switch, Route } from 'react-router-dom';
import './index.css';
// 引入组件
// 不用自己引入,当用到的时候,编辑器会自动添加
import Login from './pages.Login';
import List from './pages/admin/products/List';

ReactDOM,render(<Router>
    <Switch>
        <Route path="/login" component={Login} />
        <Route path="/admin/products" component={List} />
    </Switch>
</Router>, document.getElementById('root'));
React 里面的 index.js 相当于 Vue 的 main.js 文件

2)正常用法

在开发中一般不会这样写,而是在根目录下建一个 routes 目录,在 routes 目录下建 index.js 文件,添加如下代码:

import Login from "../pages/Login";
import Index from "../pages/admin/dashboard/Index";
import List from "../pages/admin/products/List";
import Edit from "../pages/admin/products/Edit";

// 普通页面路由
export const mainRoutes = [{
    path: '/login',
    component: Login
}, {
    path: '/404',
    component: PageNotFound
}]

// 需要鉴权的路由
export const adminRoutes = [{
    path: '/admin/dashboard',
    component: Index
}, {
    path: '/admin/products',
    component: List,
    exact: true // 路由path和下面重复了,需要设置一下,只匹配当前path
}, {
    path: '/admin/products/edit/:id',
    component: Edit
}]

然后 index.js 文件可以改成下面这样:

import React from 'react';
import ReactDOM from 'react-dom';
// 引入路由模块,使用哈希路由
import {
    HashRouter as Router,
    Switch,
    Route,
    Redirect
} from 'react-router-dom';
import './index.css';
import { mainRoutes } from "./routes";

ReactDOM,render(
    <Router>
        <Switch>
            { mainRoutes.map(route => {
                // return <Route key={route.path} path={route.path} component={route.component} />
                // 由于route里面的key也是path和component,所以可以直接用ES6延展运算符
                return <Route key={route.path} {...route} />
            }) }
            <!-- 当找不到页面的时候就重定向到404 -->
            <Redirect to="/404" />
        </Switch>
    </Router>,
    document.getElementById('root')
);

3)嵌套路由

当访问的地址以 /admin 开头的时候,都走 App 这个容器。

index.js 文件中再加一个 Route,代码如下:

import React from 'react';
import ReactDOM from 'react-dom';
import {
    HashRouter as Router,
    Switch,
    Route,
    Redirect
} from 'react-router-dom';
import './index.css';
import App from "./App";
import { mainRoutes } from "./routes";

ReactDOM,render(
    <Router>
        <!-- 这里再加一个Route,把路由属性传过去 -->
        <Route path="/admin" render={routeProps => <App {...routeProps} />} />
        <Switch>
            { mainRoutes.map(route => {
                return <Route key={route.path} {...route} />
            }) }
            <Redirect to="/404" />
        </Switch>
    </Router>,
    document.getElementById('root')
);

然后在 App.js 里面添加如下代码:

import React from 'react';
import { Switch, Route, Redirect } from 'react-router-dom';
import { adminRoutes } from './routes';

function App() {
    return (
        <div className="App">
            <h1>这是App组件</h1>
            <Switch>
                {adminRoutes.map(route => {
                    return (
                        <Route
                            key={route.path}
                            path={route.path}
                            exact={route.exact}
                            render={routeProps => {
                                return <route.component {...routeProps} />
                            }}
                        />
                    )
                })}
                <Redirect to="/404" />
            </Switch>
        </div>
    )
}

export default App
React 里面的 <Switch> 相当于 Vue 的 <router-view>

问题:Vue 可以直接定义嵌套路由,React 可以吗

5. 搭建页面框架

1)组件中引入图片

import React from 'react';
import logo from "./logo.png";

function App() {
    return (
        <div className="logo">
            <img src={logo} alt="logo" />
        </div>
    )
}

export default App

2)定义行内样式

import React from 'react';

function App() {
    return (
        <div style={{ background: "#fff" }}>
        </div>
        <div style={{ height: "100%", borderRight: 0 }}>
        </div>
        <div style={{ padding: "0 24px 24px" }}>
        </div>
        <div style={{ margin: "16px 0" }}>
        </div>
    )
}

export default App
在 React 中 {} 内部其实就是 JS 表达式

样式其实是以 JS 对象形式定义的

style 看上去是双大括号,但其实两层括号是不一样的,外层括号代表里面定义的是 JS 表达式,内层括号代表 JS 对象

3)路由点击跳转

import React from 'react';
// 在组件中使用路由需要用到 withRouter 插件,否则会报错
import { withRouter } from "react-router-dom";
// 在组件中引入 antd
import { Layout, Menu, Breadcrumb, Icon } from "antd";
import { adminRoutes } from "../../routes/index";

const routes = adminRoutes.filter(route => route.isShow);
function Index() {
    return (
        <Menu
            mode="inline"
            defaultSelectedKeys={["1"]}
            defaultOpenKeys={["sub1"]}
            style={{ height: "100%", borderRight: 0 }}
        >
            {routes.map(route => {
                return (
                    <Menu.Item
                        key={route.path}
                        onClick={p => props.history.push(p.key)}
                    >
                        <Icon type={route.icon}
                        {route.title}
                    </Menu.Item>
                )
            })}
        </Menu>
    )
}

export default withRouter(Index); // 这里需要把组件套进去
问题:Vue 可以直接通过全局方法 this.$router.push() 实现路由跳转,React 没有吗

问题:React 的 antd 组件能否全局注册

4)列表页面搭建

import React from 'react';
import { Card, Table, Button, Popconfirm } from "antd";

// 数据源
const dataSource = [{
    id: 1,
    name: "Jack",
    price: 8888
}, {
    id: 2,
    name: "Pony",
    price: 2333
}, {
    id: 3,
    name: "Eric",
    price: 9999
}]

function List() {
    // 表头字段
    const columns = [{
        title: '序号',
        key: "id",
        width: 80,
        align: "center",
        render: (txt, record, index) => index + 1 // 自定义字段类型
    }, {
        title: "名字",
        dataIndex: "name" // 对应 dataSource 里面的 name 字段
    }, {
        title: "价格",
        dataIndex: "price" // 对应 dataSource 里面的 price 字段
    }, {
        title: "操作",
        render: (txt, record, index) => {
            return (
                <div>
                    <Button type="primary" size="small">修改</Button>
                    <Popconfirm
                        title="确定删除此项?"
                        onCancel={() => console.log("用户取消删除!")}
                        onConfirm={() => console.log("用户确认删除!")}
                    >
                        <Button style={{ margin: "0 1rem" }} size="small">删除</Button>
                    </Popconfirm>
                </div>
            )
        }
    }]
    
    return (
        <Card
            title="商品列表"
            extra={
                <!-- 这里是扩展项 -->
                <Button type="primary" size="small">
                    新增
                </Button>
            }
        >
            <Table columns={columns}
                   bordered
                   dataSource={dataSource}
            />
        </Card>
    )
}

export default List;
React 列表自定义字段类型通过 render 实现, 在 Vue 中则是通过 slot 实现

6. 函数组件和 Class 组件

函数组件适合一些比较简单的应用场景,只接受外面传进来的 props , 没有自己的私有数据 state 和生命周期:

function Clock(props) {
  return (
    <div>
      <h1>Hello, world!</h1>
      <h2>It is {props.date.toLocaleTimeString()}.</h2>
    </div>
  );
}

如果需要用到私有数据 state 和生命周期就要创建 Class 组件,Class 需要继承 React.Component

class Clock extends React.Component {
  constructor(props) {
    super(props);
    this.state = {date: new Date()};
  }

  render() {
    return (
      <div>
        <h1>Hello, world!</h1>
        <h2>It is {this.state.date.toLocaleTimeString()}.</h2>
      </div>
    );
  }
}
Class 组件的 render() 方法返回的东西就是函数组件里面的内容

render() 方法里面需要通过 this 访问 propsstate

7. 事件处理

在 React 中事件的命名采用小驼峰法,而在传统 HTML 中是纯小写。

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

在 React 中必须显式地使用 preventDefault 阻止默认事件,而在传统 HTML 中可以直接 return false ,代码如下:

function ActionLink() {
  function handleClick(e) {
    e.preventDefault();
    console.log('The link was clicked.');
  }

  return (
    <a href="#" onClick={handleClick}>
      Click me
    </a>
  );
}
在这里,e 是一个合成事件。React 事件与原生事件不完全相同。

1)事件处理函数 this 的绑定

a)在构造器中绑定 this

在 JavaScript 中,class 的方法默认不会绑定 this。如果你忘记绑定 this.handleClick 并把它传入了 onClick,当你调用这个函数的时候 this 的值为 undefined

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 ? 'ON' : 'OFF'}
      </button>
    );
  }
}

如果觉得使用 bind 很麻烦,这里有两种方式可以解决。

b)使用 class fields

一种是可以使用 class fields 正确的绑定回调函数,Create React App 默认启用此语法:

class LoggingButton extends React.Component {
  // 此语法确保 `handleClick` 内的 `this` 已被绑定。
  // 注意: 这是 *实验性* 语法。
  handleClick = () => {
    console.log('this is:', this);
  }

  render() {
    return (
      <button onClick={this.handleClick}>
        Click me
      </button>
    );
  }
}

c)使用箭头函数

另一种是可以在回调中使用箭头函数,此语法问题在于每次渲染 LoggingButton 时都会创建不同的回调函数。在大多数情况下,这没什么问题,但如果该回调函数作为 prop 传入子组件时,这些组件可能会进行额外的重新渲染:

class LoggingButton extends React.Component {
  handleClick() {
    console.log('this is:', this);
  }

  render() {
    // 此语法确保 `handleClick` 内的 `this` 已被绑定。
    return (
      <button onClick={() => this.handleClick()}>
        Click me
      </button>
    );
  }
}

2)向事件处理函数传递参数

下面两种方式是等价的。如果通过箭头函数的方式,事件对象必须显式的进行传递,而通过 bind 的方式,事件对象以及更多的参数会被隐式的进行传递。

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

8. 条件渲染

基础用法:

class LoginControl extends React.Component {
  constructor(props) {
    super(props);
    this.state = {isLoggedIn: false};
  }

  render() {
    const isLoggedIn = this.state.isLoggedIn;
    let button;
    if (isLoggedIn) {
      button = <LogoutButton onClick={this.handleLogoutClick} />;
    } else {
      button = <LoginButton onClick={this.handleLoginClick} />;
    }

    return (
      <div>
        <Greeting isLoggedIn={isLoggedIn} />
        {button}
      </div>
    );
  }
}

与运算符 &&

render() {
  const count = 0;
  return (
    <div>
      { count && <h1>Messages: {count}</h1>}
    </div>
  );
}
之所以能这样做,是因为在 JavaScript 中,true && expression 总是会返回 expression, 而 false && expression 总是会返回 false。因此,如果条件是 true&& 右侧的元素就会被渲染,如果是 false,React 会忽略并跳过它。

在上面的代码中,如果 count 的值为0,还是会进行渲染。

三元运算符

render() {
  const isLoggedIn = this.state.isLoggedIn;
  return (
    <div>
      {isLoggedIn
        ? <LogoutButton onClick={this.handleLogoutClick} />
        : <LoginButton onClick={this.handleLoginClick} />
      }
    </div>
  );
}

9. 状态提升

实际上就是子组件向父组件传参

子组件代码:

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>
    );
  }
}

父组件代码:

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});
  }

  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} />
        <BoilingVerdict
          celsius={parseFloat(celsius)} />
      </div>
    );
  }
}
在 React 中使用 this.props.onTemperatureChange(e.target.value); 向外传递事件同时携带参数

相当于 Vue 中的 this.$emit("onTemperatureChange", e.targetvalue)

10. react-redux

  • redux三个基本原则:
    ①:store必须是唯一的
    ②:只有store可以改变自己的内容
    ③:reducer 必须是纯函数
  • 只有store能改变自己内容说明在reducer里我们不能直接操作state,只能通过定义新变量copy state的值,然后对新变量进行操作并 return 出新变量,不允许直接改变state。
  • 什么是纯函数?
    给固定的输入,就一定会有固定的输出,并且不会有任何副作用。
    所以对于异步函数(定时器、ajax数据请求等)、动态时间都不适意在reducer里订阅。
  • store核心api:
    crerteStore(): 创建一个store
    store.dispatch(action): 派发action,传递store
    store.getState(): 获取store的所有数据
    store.subscribe(): 订阅store的变化,接收的回调函数在store改变时候就会自动执行

一杯绿茶
199 声望17 粉丝

人在一起就是过节,心在一起就是团圆