前端是充满想象力的。从react开始。

todo: 做一个完整的admin管理后台,实现平时后台的服务和方法的集合。

所以,我选择通过React去实现一个后端开发者会经常DIY的管理端项目。

基于React实现的后端管理项目,站在巨人的肩膀上一步一步实现一个。

React参考资料

  1. 参考网站 React官网文档
  2. 参考书籍《深入React技术栈》
  3. 参考书籍《React in action》

React开发环境

采用webpack构建React开发环境。

基础环境准备

  1. 安装nodejs稳定版本
  2. nodejs安装yarn的包管理工具

初始化项目

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
# 查看yarn的源
yarn config get registry
# 设置yarn的源为国内源
yarn config set registry https://registry.npm.taobao.org/
# npx create 项目
mkdir react-demo
cd react-demo 
# git 初始化
git init .
# yarn初始化,如果yarn初始化失败,可以使用npm init
yarn init
# 创建src文件夹以及src.index.js的js主逻辑文件
mkdir src
cd src 
touch index.js
# 创建网页主入口文件index.html
touch index.html

webpack.config.js

webpack逻辑文件

1
touch webpack.config.js

其中webpack.config.js的主要内容

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')

module.exports = {
    entry: path.join(__dirname, './src/index.js'),
    output: {
        path: path.join(__dirname, './dist'),
        filename: 'bundle.js'
    },
    devServer: {
        port: '8800',
        open: true
    },
    mode: 'development',
    module: {
        rules: [
            { test: /\.m?js|jsx$/, use: 'babel-loader', exclude: /node_modules/},
            { test: /\.css$/, use: ['style-loader', 'css-loader']}
        ]
    },
    plugins: [
        new HtmlWebpackPlugin({
            template: path.join(__dirname, './index.html')
        })
    ]
}

babelrc

对react语法和jsx文件的转义

1
touch .babelrc

其中 .babelrc 的内容

1
2
3
4
5
6
{
    "presets": [
        "env",
        "react"
    ]
}

导入依赖

1
2
3
4
5
6
7
8
# 安装webpck依赖
yarn add webpack webpack-cli webpack-dev-server html-webpack-plugin --dev
# 安装babel编译依赖
yarn add babel-core babel-loader@7.1.5 babel-preset-env babel-preset-react --dev
# 必要的css样式依赖
yarn add css-loader style-loader --dev
# 安装react的依赖
yarn add react react-dom

运行

1
2
3
4
5
6
7
8
9
yarn run dev

# babel-loader版本到8.x,不适配, 需要降低版本
yarn remove babel-loader
yarn add babel-loader@7.1.5 --dev

# 目前react已到18.0.0, 由于18改动和我以前看的基础的react差距有点,我退回到17
yarn remove react react-dom
yarn add react@17.0.0 react-dom@17.0.0

React概念

可变和不可变

可变: 不可变的持久数据结构, 不能直接覆盖,存在版本,但是随着时间的变化存在多个版本

不可变: 可变的临时数据结构,可直接覆盖,不存在版本,随着时间变化只存在一个版本

渲染和生命周期

react life cycle

will 将执行 在一些事情发生前被调用

did 已完成 在一些事情发生后被调用

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
import React from "react";
import PropTypes from "prop-types";

class ChildComponent extends React.Component {
  static propTypes = {
    name: PropTypes.string
  };

  static defaultProps = (function() {
    console.log("ChildComponent : defaultProps");
    return {};
  })();


  constructor(props) {
    super(props);
    console.log("ChildComponent: state");
    this.state = {
      name: "Mark"
    };
    this.oops = this.oops.bind(this);
  }

  componentWillMount() {
    console.log("ChildComponent : componentWillMount");
  }

  componentDidMount() {
    console.log("ChildComponent : componentDidMount");
  }

  componentWillReceiveProps(nextProps) {
    console.log("ChildComponent : componentWillReceiveProps()");
    console.log("nextProps: ", nextProps);
  }

  shouldComponentUpdate(nextProps, nextState) {
    console.log("<ChildComponent/> - shouldComponentUpdate()");
    console.log("nextProps: ", nextProps);
    console.log("nextState: ", nextState);
    return true;
  }

  componentWillUpdate(nextProps, nextState) {
    console.log("<ChildComponent/> - componentWillUpdate");
    console.log("nextProps: ", nextProps);
    console.log("nextState: ", nextState);
  }

  componentDidUpdate(previousProps, previousState) {
    console.log("ChildComponent: componentDidUpdate");
    console.log("previousProps:", previousProps);
    console.log("previousState:", previousState);
  }

  componentWillUnmount() {
    console.log("ChildComponent: componentWillUnmount");
  }

  oops() {
    this.setState(() => ({ oops: true }));
  }

  render() {
    if (this.state.oops) {
      throw new Error("Something went wrong");
    }
    console.log("ChildComponent: render");
    return [
      <div key="name">Name: {this.props.name}</div>,
      <button key="error" onClick={this.oops}>
        Create error
      </button>
    ];
  }
}

class ParentComponent extends React.Component {
  static defaultProps = (function() {
    console.log("ParentComponent: defaultProps");
    return {
      true: false
    };
  })();

  constructor(props) {
    super(props);
    console.log("ParentComponent: state");
    this.state = { text: "" };
    this.onInputChange = this.onInputChange.bind(this);
  }
  componentWillMount() {
    console.log("ParentComponent: componentWillMount");
  }
  componentDidMount() {
    console.log("ParentComponent: componentDidMount");
  }
  componentWillUnmount() {
    console.log("ParentComponent: componentWillUnmount");
  }
  onInputChange(e) {
    const text = e.target.value;
    this.setState(() => ({ text: text }));
  }
  componentDidCatch(err, errorInfo) {
    console.log("componentDidCatch");
    console.error(err);
    console.error(errorInfo);
    this.setState(() => ({ err, errorInfo }));
  }
  render() {
    console.log("ParentComponent: render");
    if (this.state.err) {
      return (
        <details style={{ whiteSpace: "pre-wrap" }}>
          {this.state.error && this.state.error.toString()}
          <br />
          {this.state.errorInfo.componentStack}
        </details>
      );
    }
    return [
      <h2 key="h2">Learn about rendering and lifecycle methods!</h2>,
      <input
        key="input"
        value={this.state.text}
        onChange={this.onInputChange}
      />,
      <ChildComponent key="ChildComponent" name={this.state.text} />
    ];
  }
}

export default ParentComponent;

直到React在实际DOM中创建组件为止,组件只会存在于虚拟DOM中。

挂载方法在组件生命周期的开始和结束只会被出发一次,组件只有一个开始和结束。

react mount

React-DOM负责组件的挂载和卸载。

组件挂载是将组建插入实际DOM的过程。卸载是将组件从DOM中删除。

react component mount

React实践

  • createElementJSX
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
// 导入react和react-dom
import React from 'react'
import ReatDom from 'react-dom'

// 通过createElement创建元素、元素属性和元素字节点
// 返回虚拟DOM对象
const dv = React.createElement('div', {
    'title': 'divEle'
}, 'Hello React!')

// JSX 的方式实现
let user = {
    name: 'react',
    age: 18
}

let arr = [1, 2, 3, 4]

const dv = (
    <div title='divEle'>
        <p>Name {user.name}, Age {user.age}</p>
        <p>{arr.join('-')}</p>
        <p>
            {
                arr.map((res, index) => {
                    return (
                        <span key={index}>{res}</span>
                    )
                })
            }
        </p>
    </div>
)

// 渲染虚拟DOM
// 参数1 渲染对象
// 参数2 渲染的位置
ReatDom.render(dv, document.getElementById('root'))
  • 组件 component
  1. 函数组件
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
// 组件首字母大写,区别于普通DOM元素
// 组件返回必须由一个根元素包裹的内容,必须有返回,即使return null
function Hello() {
    return (
        <div title="hello react">
            Hello React
        </div>
    )
}

// 渲染虚拟DOM
// 组件按照HTML的方式加载
ReatDom.render(<Hello/>, document.getElementById('root'))
  1. 带props的函数组件
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
// props可以接收任何类型的值
// props是只读的 不可改变
function HelloProps(props){
    console.log(props);

    // props.name = 'react';
    props.fn();

    return (
        <div>name:{props.name} age:{props.age}</div>
    );
}

ReatDom.render(<HelloProps name="react" age="18" arr={[1,2,3]} fn={()=>{console.log(1234)}} obj={{name:"react"}}/>, document.getElementById('root'))
  1. 类组件
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
// 类组件继承 React.Component,继承其props的对象属性
// 必须存在render(){return(null)}两个方法
class Hello  extends React.Component {
    render() {
        return (
             <div>
                 <p>Hello React 类组件</p>
                 <p>{this.props.name}</p>
             </div>
        );
    }
}

ReatDom.render(<Hello name="react"/>, document.getElementById('root'))

4。 类组件的props和state

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class Hello  extends React.Component {
    // props 组件数据对象
    constructor(props){
        super(props)
        
        // state 管理组件内部数据的固定属性
        // state 默认为null
        // 在constructor中初始化
        // 尽可能保证其中都是用于渲染的内容,加快VDOM的Diff
        this.state = {
            count: 2,
            tech: ['golang', 'python']
        }
    }
    render() {
        this.state.count = 3
        this.state.tech.push('react')
        return (
             <div>
                 <p>React 类组件</p>
                 <p>范围: {this.props.name}, 数量: {this.state.count}</p>
                 <p>种类: {this.state.tech}</p>
             </div>
        );
    }
}

ReatDom.render(<Hello name="前端"/>, document.getElementById('root'))
  1. 样式的加载
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
// 所有属性都是驼峰式命名 特别hi是className等
 render() {
    this.state.count = 3
    this.state.tech.push('react')
    return (
        <div>
            <p>React 类组件</p>
            <p>范围: {this.props.name}, 数量: {this.state.count}</p>
            <p>种类: </p>
            <ul style={{listStyle: 'none', margin: 0, padding: 0}} className='cla'>
                {
                    this.state.tech.map((value, index)=>{
                        return (
                            <li style={{marginBottom:'10px', border: 'line'}} key={index}>{value}</li>
                        )
                    })
                }
            </ul>
        </div>
    );
}