前端框架


React

创建react项目

npm install create-react-app
npx create-react-app myapp --template typescripe

如果create-react-app是全局安装的,那么前面的npx/npm可以省略

启动流程

重点关注三个文件:

src/App.js //实现 React 组件

src/index.js // React 世界的入口

public/index.html //挂载的页面

1.index.html

这个是一个模板文件,里面有过id为root的div标签,会被index.js文件中的JSX替换这个DOM节点。


2.index.js

关键就是这个ReactDOM.render()

简单来说,ReactDOM.render() 会使用你的 JSX 来替换你的 HTML 中的一个 DOM 节点。这
样你就可以很容易地把 React 集成到每一个其他的应用中。ReactDOM.render() 可以在你的
应用中被多次使用。你可以在多个地方使用它来加载简单的 JSX 语法、单个 React 组件、
多个 React 组件或者整个应用。但是在一个纯 React 的应用中,你只会使用一次用来加载你
的整个组件树。
ReactDOM.render() 有两个传入参数。第一个是准备渲染的 JSX。第二个参数指定了 React
应用在你的 HTML 中的放置的位置。这个位置是一个 id='root' 的元素。你可以在文件
public/index.html 中找到这个 id 属性


3.App.js
在inde.js文件里面,ReactDOM.render() 总会很好地渲染你的 App 组件。一般来说,某个组件叫xxx,那么他的代码就叫xxx.js。要想知道页面被渲染成什么样,就看看里面内容。

react-router-dom路由

1.这是react-router-dom5的用法,现在默认安装的是6,下面会写6的语法

原文章链接
来源:简书

使用背景:使用React构建的单页面应用,要想实现页面间的跳转,首先想到的就是使用路由。在React中,常用的有两个包可以实现这个需求,那就是react-router和react-router-dom。本文主要针对react-router-dom进行说明。

安装

首先进入项目目录,使用npm安装react-router-dom:

npm install react-router-dom --save-dev //这里可以使用cnpm代替npm命令;

//--save-dev 标记表示该 node 包只是用作开发环境的一部分,并不会被作为你产品代码的一
部分发布。哪种 node 包适用这个场景呢?设想你需要一些 node 包辅助测试你的应用,然
后需要通过 npm 来安装这些包,但是不希望他们混入产品代码里面。测试过程应该只会发
生在开发阶段,而不是在线上部署运行的时候。因为那个时候已经用不到测试代码了,你
的应用应该已经被测试完而且可以被你的用户使用了。这可能是你唯一的使用 --save-dev
的场景。

基本操作

然后我们新建两个页面,分别命名为“home”和“detail”。在页面中编写如下代码:
detail.js

import React from 'react';

export default class Home extends React.Component {
    render() {
        return (
            <div>
                <a>去detail</a>
            </div>
        )
    }
}

home.js

import React from 'react';

export default class Home extends React.Component {
    render() {
        return (
            <div>
                <a>回到home</a>
            </div>
        )
    }
}

然后再新建一个路由组件,命名为“Router.js”,并编写如下代码:

import React from 'react';
import {HashRouter, Route, Switch} from 'react-router-dom';
import Home from '../home';
import Detail from '../detail';


const BasicRoute = () => (
    <HashRouter>
        <Switch>
            <Route exact path="/" component={Home}/>
            <Route exact path="/detail" component={Detail}/>
        </Switch>
    </HashRouter>
);

export default BasicRoute;

如上代码定义了一个纯路由组件,将两个页面组件Home和Detail使用Route组件包裹,外面套用Switch作路由匹配,当路由组件检测到地址栏与Route的path匹配时,就会自动加载响应的页面。
然后在入口文件中——我这里指定的是index.js——编写如下代码:

import React from 'react';
import ReactDOM from 'react-dom';
import Router from './router/router';

ReactDOM.render(
  <Router/>,
  document.getElementById('root')
);

这里相当于向页面返回了一个路由组件。我们先运行项目看一下效果,在地址栏输入“http://localhost:3000/#/”:

输入“http://localhost:3000/#/detail”:

通过a标签跳转
可以看到其实路由已经开始工作了,接下来我们再来做页面间的跳转。在home.js和detail.js中,我们修改如下代码:

import React from 'react';


    export default class Home extends React.Component {
        render() {
            return (
                <div>
                <a href='#/detail'>去detail</a>
            </div>
        )
    }
}

home.js

import React from 'react';

export default class Home extends React.Component {
    render() {
        return (
            <div>
                <a href='#/'>回到home</a>
            </div>
        )
    }
}

重新打包运行,在浏览器地址栏输入“http://localhost:3000/”,试试看页面能否正常跳转。如果不能,请按步骤一步一步检查代码是否有误。以上是使用a标签的href进行页面间跳转,此外react-router-dom还提供了通过函数的方式跳转页面。

通过函数跳转

首先我们需要修改router.js中的两处代码:
然后在home.js中:
import React from ‘react’;

...
import {HashRouter, Route, Switch, hashHistory} from 'react-router-dom';
...
<HashRouter history={hashHistory}>
...

在a标签下面添加一个按钮并加上onClick事件,通过this.props.history.push这个函数跳转到detail页面。在路由组件中加入的代码就是将history这个对象注册到组件的props中去,然后就可以在子组件中通过props调用history的push方法跳转页面。

很多场景下,我们还需要在页面跳转的同时传递参数,在react-router-dom中,同样提供了两种方式进行传参。

链接:https://www.jianshu.com/p/8954e9fb0c7e
来源:简书

export default class Home extends React.Component {
    constructor(props) {
        super(props);
    }
    
    
    render() {
        return (
            <div>
                <a href='#/detail'>去detail</a>
                <button onClick={() => this.props.history.push('detail')}>通过函数跳转</button>
            </div>
        )
    }
}

url传参
在router.js中,修改如下代码:

...
<Route exact path="/detail/:id" component={Detail}/>
...

然后修改detail.js,使用this.props.match.params获取url传过来的参数:

...
componentDidMount() {
    console.log(this.props.match.params);
}
...

在地址栏输入“http://localhost:3000/#/detail/3”,打开控制台:

可以看到传过去的id=3已经被获取到了。react-router-dom就是通过“/:”去匹配url传递的参数。

隐式传参
此外还可以通过push函数隐式传参。

修改home.js代码如下:

import React from 'react';


export default class Home extends React.Component {
    constructor(props) {
        super(props);
    }
    
    
    render() {
        return (
            <div>
                <a href='#/detail/3'>去detail</a>
                    <button onClick={() => this.props.history.push({
                        pathname: '/detail',
                        state: {
                            id: 3
                        }
                })}>通过函数跳转</button>
            </div>
        )
    }
}

在detail.js中,就可以使用this.props.history.location.state获取home传过来的参数:

componentDidMount() {
    //console.log(this.props.match.params);
    console.log(this.props.history.location.state);
}

跳转后打开控制台可以看到参数被打印:

嵌套路由

嵌套路由的适用场景还是比较多的,接下来就来介绍一下实现方法。
首先在Vue中实现嵌套路由,只需要将配置文件写成children嵌套,然后在需要展示子路由的位置加上</router-view>即可。React中应该如何实现呢?其实原理和Vue类似,只需要在父级路由中包含子路由即可。这样说可能很多同学会一头雾水,直接上代码(不使用上面的例子):

首先定义父级组件MainLayout

import React from 'react';
import './MainLayout.scss';

const { Header, Sider, Content } = Layout;


export default class MainLayout extends React.Component {

    render() {
        return (
            <div className='main-layout'>
                父组件
            </div>
        );
    }
}

然后定义子组件Home:

import React, {useState} from 'react';
import {Modal, Select} from "antd";
import {connect} from 'react-redux';
import {addCount} from '../../servers/home';


function Home(props) {
    const [visible, setVisible] = useState(false);
    const {countNum: {count}, dispatch} = props;

    return (
        <div>
            子组件
        </div>
    )
}

export default Home;

然后将它们添加进路由router.js,并且关联父子关系:

import React from 'react';
import {HashRouter, Route, Switch} from "react-router-dom";
import Home from '../pages/Home/Home';
import MainLayout from '../layout/MainLayout';

const BasicRouter = () => (
    <HashRouter>
        <Switch>
            <Route path="/index" component={
                <MainLayout>
                  <Route exact path="/" component={Home}/>
                  <Route exact path="/index" component={Home}/>
                  <Route path="/index/home" component={Home}/>
                </MainLayout>
             }/>
        </Switch>
    </HashRouter>
);


export default BasicRouter;

在MainLayout中,修改如下代码:

import React from 'react';
import './MainLayout.scss';

const { Header, Sider, Content } = Layout;


export default class MainLayout extends React.Component {

    render() {
        return (
            <div className='main-layout'>
                {this.props.children}
            </div>
        );
    }
}

如此,一个嵌套路由就完成了。

2.react-router-dom版本6语法

< Switch > -> < Routes >
component={hello} -> element={< hello />}

组件

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

1.函数组件
定义组件最简单的方式就是编写 JavaScript 函数:

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

该函数是一个有效的 React 组件,因为它接收唯一带有数据的 “props”(代表属性)对象与并返回一个 React 元素。这类组件被称为“函数组件”,因为它本质上就是 JavaScript 函数。

2.class组件

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

上述两个组件在 React 里是等效的。

Props

原文

组件从概念上看就是一个函数,可以接受一个参数作为输入值,这个参数就是props,所以可以把props理解为从外部传入组件内部的数据。由于React是单向数据流,所以props基本上也就是从服父级组件向子组件传递的数据。

用法

function Hello(props){
    return <div>hello world  +  {props.name}</div>
}

React.render(
    <Hello name="zhangshan"/>,
    document.getElementById('app')
)

‘<’ Hello name=”zhangshan”/‘>’,是什么意思?

为什么我们在方法里面获取的数据是{props.name}里面获取到我们传递的name的?

其实就是我们把参数name=”zhangshan” 放在了props里,props这个东西不需要我们定义,这个是react里面就存在的一个东西,专门用来存放我们的要传递的参数的。

但是在我们的function Hello(props)里面,必须显式地传入我们的props。

那如果要传递很多参数呢?

function Hello(props){
    return <div>hello world  +  {props.name} + {props.id}</div>
}

React.render(
    <Hello name="zhangshan" id={12}/>,
    document.getElementById('app')
)

相当于,我们把name和id都放入了props里面。props里面到底能够容纳多少的参数,目前我们不考虑。

但是我们发现id={12},这个参数的样式跟到我们的name=”zhangshan”不一样,为什么呢?

id我们需要传入的是一个数字,所以我们不能用分号,如果我们用分号的话id=“12”,那么12就是一个字符串了。

最后还有一个很重要的点:props是只读的

什么是只读呢?

意思是任何修改props里面参数的操作,都是错误的!例如下面:

function Hello(props){
    props.name="lisi"
    return <div>hello world  +  {props.name} + {props.id}</div>
}

只读性

props经常被用作渲染组件和初始化状态,当一个组件被实例化之后,它的props是只读的,不可改变的。如果props在渲染过程中可以被改变,会导致这个组件显示的形态变得不可预测。只有通过父组件重新渲染的方式才可以把新的props传入组件中

默认参数

在组件中,我们最好为props中的参数设置一个defaultProps,并且制定它的类型。比如,这样:

Item.defaultProps = {
  item: 'Hello Props',
};

Item.propTypes = {
  item: PropTypes.string,
};

关于propTypes,可以声明为以下几种类型:

optionalArray: PropTypes.array,
optionalBool: PropTypes.bool,
optionalFunc: PropTypes.func,
optionalNumber: PropTypes.number,
optionalObject: PropTypes.object,
optionalString: PropTypes.string,
optionalSymbol: PropTypes.symbol,

注意,bool和func是简写。
复杂数据类型

总结

props是一个从外部传进组件的参数,主要作为就是从父组件向子组件传递数据,它具有可读性和不变性,只能通过外部组件主动传入新的props来重新渲染子组件,否则子组件的props以及展现形式不会改变。

state

原文

State is similar to props, but it is private and fully controlled by the component.

用法

export default class ItemList extends React.Component{
  constructor(){
    super();
    this.state = {
      itemList:'一些数据',
    }
  }
  render(){
    return (
      {this.state.itemList}
    )
  }
}

首先,在组件初始化的时候,通过this.state给组件设定一个初始的state,在第一次render的时候就会用这个数据来渲染组件。
如果要传参数,constructor、super括号里面都要加入props

setState

state不同于props的一点是,state是可以被改变的。不过,不可以直接通过this.state=的方式来修改,而需要通过this.setState()方法来修改state。

比如,我们经常会通过异步操作来获取数据,我们需要在didMount阶段来执行异步操作:

componentDidMount(){
  fetch('url')
    .then(response => response.json())
    .then((data) => {
      this.setState({itemList:item});  
    }
}

当数据获取完成后,通过this.setState来修改数据状态。

当我们调用this.setState方法时,React会更新组件的数据状态state,并且重新调用render方法,也就是会对组件进行重新渲染。

注意:通过this.state=来初始化state,使用this.setState来修改state,constructor是唯一能够初始化的地方。

setState接受一个对象或者函数作为第一个参数,只需要传入需要更新的部分即可,不需要传入整个对象,比如:

export default class ItemList extends React.Component{
  constructor(){
    super();
    this.state = {
      name:'axuebin',
      age:25,
    }
  }
  componentDidMount(){
    this.setState({age:18})  
  }
}

在执行完setState之后的state应该是{name:’axuebin’,age:18}。

setState还可以接受第二个参数,它是一个函数,会在setState调用完成并且组件开始重新渲染时被调用,可以用来监听渲染是否完成:

this.setState({
  name:'xb'
},()=>console.log('setState finished'))

总结

state的主要作用是用于组件保存、控制以及修改自己的状态,它只能在constructor中初始化,它算是组件的私有属性,不可通过外部访问和修改,只能通过组件内部的this.setState来修改,修改state属性会导致组件的重新渲染。

区别

  • state是组件自己管理数据,控制自己的状态,可变;
  • props是外部传入的数据参数,不可变;
  • 没有state的叫做无状态组件,有state的叫做有状态组件;
  • 多用props,少用state。也就是多写无状态组件。

ant design

使用方法。

打开组件对应的源码,把里面的东西复制出来。

import { Layout, Menu, Breadcrumb, Icon } from 'antd';
import React, { Component } from "react";
import "antd/dist/antd.css";


const { SubMenu } = Menu;
const { Header, Content, Sider } = Layout;

class SiderDemo extends Component {
  render(){
     return(
  <Layout>
    <Header className="header">
      <div className="logo" />
      <Menu
        theme="dark"
        mode="horizontal"
        defaultSelectedKeys={['2']}
        style={{ lineHeight: '64px' }}
      >
        <Menu.Item key="1">nav 1</Menu.Item>
        <Menu.Item key="2">nav 2</Menu.Item>
        <Menu.Item key="3">nav 3</Menu.Item>
      </Menu>
    </Header>
    <Layout>
      <Sider width={200} style={{ background: '#fff' }}>
        <Menu
          mode="inline"
          defaultSelectedKeys={['1']}
          defaultOpenKeys={['sub1']}
          style={{ height: '100%', borderRight: 0 }}
        >
          <SubMenu key="sub1" title={<span><Icon type="user" />subnav 1</span>}>
            <Menu.Item key="1">option1</Menu.Item>
            <Menu.Item key="2">option2</Menu.Item>
            <Menu.Item key="3">option3</Menu.Item>
            <Menu.Item key="4">option4</Menu.Item>
          </SubMenu>
          <SubMenu key="sub2" title={<span><Icon type="laptop" />subnav 2</span>}>
            <Menu.Item key="5">option5</Menu.Item>
            <Menu.Item key="6">option6</Menu.Item>
            <Menu.Item key="7">option7</Menu.Item>
            <Menu.Item key="8">option8</Menu.Item>
          </SubMenu>
          <SubMenu key="sub3" title={<span><Icon type="notification" />subnav 3</span>}>
            <Menu.Item key="9">option9</Menu.Item>
            <Menu.Item key="10">option10</Menu.Item>
            <Menu.Item key="11">option11</Menu.Item>
            <Menu.Item key="12">option12</Menu.Item>
          </SubMenu>
        </Menu>
      </Sider>
      <Layout style={{ padding: '0 24px 24px' }}>
        <Breadcrumb style={{ margin: '16px 0' }}>
          <Breadcrumb.Item>Home</Breadcrumb.Item>
          <Breadcrumb.Item>List</Breadcrumb.Item>
          <Breadcrumb.Item>App</Breadcrumb.Item>
        </Breadcrumb>
        <Content style={{ background: '#fff', padding: 24, margin: 0, minHeight: 280 }}>
          Content
        </Content>
      </Layout>
    </Layout>
    </Layout>
    )}
}
export default SiderDemo;

以上面的为例,组件叫做SiderDemo。return里面的内容就是源码里面ReactDOM.renderd里面的东西,这里面最后的mountNode去掉了。
其余的东西放到class外面。

引入:

组件中对a标签的href的带变量拼接

关键点是外面加了大括号

linkContent=<a href={DATA.serverUrl+'service/apartment'+url+'?apartmentId='+urlID}><p>名称:{this.props.name}</p></a>

向路由组件传递参数

原文链接

import导入时带与不带花括号{}

一个模块中只能有一个默认导出export default,但是却可以有任意命名导出export(0个、1个、多个)

  1. 不带花括号{},引用js文件中默认导出(export default)的模块

import模块时的可随意命名。export default默认导出不能加花括号{}。

  1. 带花括号{},引用js文件中export导出的同名模块

import模块时的命名必须一致。export命名导出必须加花括号{}。

React Hooks

原链接

Redux 的作者 Dan Abramov 总结了组件类的几个缺点:

  • 大型组件很难拆分和重构,也很难测试。
  • 业务逻辑分散在组件的各个方法之中,导致重复逻辑或关联逻辑。
  • 组件类引入了复杂的编程模式,比如 render props 和高阶组件。

函数组件必须是纯函数,不能包含状态,也不支持生命周期方法,因此无法取代类。

React Hooks 的设计目的,就是加强版函数组件,完全不使用”类”,就能写出一个全功能的组件。

React Hooks 的意思是,组件尽量写成纯函数,如果需要外部功能和副作用,就用钩子把外部代码”钩”进来。 React Hooks 就是那些钩子。

四个最常用的钩子:
-useState()
-useContext()
-useReducer()
-useEffect()

umijs

加载组件出错

错误信息如下:

[@umijs/runtime] load component failed Error: Module "./antd/es/select/style" does not exist in container. while loading "./antd/es/select/style" from webpack/container/reference/mf

如果增加了一个新的组件,不在构建范围之内,就会出错。
解决办法:
删除.umi缓存,然后重新启动,这个是 mfsu 没有自动编译导致的。

React 基本使用

原链接

脚手架

npm install create-react-app
npx create-react-app myapp --template typescripe

如果create-react-app是全局安装的,那么前面的npx/npm可以省略

组件开发

创建组件

Vue 创建组件 index.vue

React 创建组件 index.jsx 或者 index.tsx,jsx 顾名思义是写 JS 的,tsx 肯定就是写 TS的哈 两者创建组件区别只是后缀名不一致

HTML

Vue 是用 < template > “内容”</ template >

注意点:函数名首字母是要大写的
React 是用 return 后面的内容是用 () 包起来的,里面用的是 JSX 语法来开发组件的

// 函数式组件
export default function HelloWorld() {
    return (
        <div>HelloWorld</div>
    )
}

Class

Vue 语法: class=”类名”

React 语法: className=”类名”

export default function HelloWorld() {
    return (
        <div className="box">HelloWorld</div>
    )
}

Style

Vue 语法: style="{color: 'red'}"

React 语法 style={{ color: 'red' }}

注意点:其中 style 接受的值是一个对象,且用 {} 括号传入,而且对象的属性名只能用驼峰式来命名

export default function HelloWorld() {
        return (
            <div style={{ color: 'red' }}>HelloWorld</div>
        )
    }

组件的使用

Vue 使用组件先引入再注册最后使用组件

React 相当于函数,只有引入的概念,没有注册的概念,所以引入进来就可以使用

import HelloWorld from './HelloWorld';
    // <> </> 相当于根标签
    export default function Index() {
        return (
            <>
                <HelloWorld />
            </>
        )
    }

声明状态变量

Vue 声明变量是在 data 里面定义的
React 使用 useState 返回的第一个值,进行声明变量的

注意点:Vue 使用数据是双大括号 ,React 使用数据是单大括号

import React, { useState } from 'react';
    
    export default function HelloWorld() {
    const [title] = useState('豆豆'); // 括号里面是给初始值
    const [count] = useState(20);
        return (
            <>
                <div>{ title }</div>
                <div>{ count }</div>
            </>
        )
    }

更新数据

绑定事件

传参

父子组件通讯

插槽

常见问题

react hook 重复不停的运行

打开控制台就能看见不停的运行,原因目前还没找到,解决方法如下:
相关链接
给useEffect传递一个空数组([])作为第二个参数

useEffect(() => {
  getModuleNameInfo(+productid, +projectId)
},[]
)


文章作者: Jason Lin
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 reprint policy. If reproduced, please indicate source Jason Lin !
  目录