React项目搭建及React基本介绍(Class组件)

1,379 阅读4分钟

React项目开发基本使用

React项目开发前需要安装node环境

  1. 搜索引擎搜索node.js进入官网
  2. 安装最新稳定版即可(node集成了npm,npm是node.js的包管理工具)
  3. 安装完成后在终端中输入node -v,npm -v能输出版本信息则可正常使用(若提示命令找不到,自行搜索配置全局PATH)

搭建React项目推荐使用官方出品的脚手架create-react-app(俗称CRA)

(CRA无需安装或配置 Webpack 或 Babel 等工具。 它们是预先配置好并且隐藏的,因此你可以专注于代码,需要自定义及修改可以使用eject命令暴漏webpack配置文件)
全局安装CRA便于使用,执行npm i -g create-react-app
create-react-app [项目名]执行后会在当前目录创建项目
cd [项目名]进入项目根目录
npm run start执行命令即可启动项目
CRA文档传送门
关于npm的用法不过多介绍,自行搜索相关文档。

这是一个可供在线开发的沙箱环境:codesandbox.io/s


JSX介绍

React应用通常使用JSX语法编写程序(不是必须使用JSX),JSX是一种 JavaScript 语法扩展,有点类似于 HTML/XML。

根据官网文档介绍,React DOM 在渲染所有输入内容之前,默认会进行转义。它可以确保在你的应用中,永远不会注入那些并非自己明确编写的内容。所有的内容在渲染之前都被转换成了字符串。这样可以有效地防止 XSS(cross-site-scripting, 跨站脚本)攻击,所以可以放心食用。关于XSS可以看下面的例子:

假设我们有一个电商系统的评论功能。 用户 A 提交评论[物流很快!]到服务器,然后用户 B 来访问网站,那么B就看到了 A 的评论[物流很快!]。 用户 C 提交评论[<script>console.log(document.cookie)</script>],然后用户 B 来访问网站,这段脚本在 B 的浏览器直接执行,用户 C 的脚本就可以任意操作 B 的 cookie,有了 cookie,恶意用户 C 就可以伪造 B 的登录信息,由此所产生的安全负面影响不言而喻。


这是一个JSX示例

const ReactNode = ()=> (
  <div
    className="my-class"
    //className={`my-class`}也可以使用JavaScript表达式
    style={{ color: `red`,marginLeft: 8 }}
    //内联样式使用对象写法第一个{}标注此处为JavaScript表达式第二个括号为对象
    onClick={(event) => {
      console.log(event.currentTarget.tagName);
    }}
  >
    {window.innerHeight}
    {/*这是注释...*/}
  </div>
)

上面基础示例这种不是字符串,也不是HTML的写法就是JSX语法。

书写JSX中需要注意的地方:

  1. React组件首字母必须大写,小写字母开头的会被认为HTML内置组件,使用小写的React组件名会报错。
  2. JSX中用{}标识表示此处{}中的代码为JavsScript表达式。
  3. 属性名遵循驼峰命名规范。
  4. 使用className与htmlFor代替class和for。
  5. 组件与组件之间是可以嵌套的。
  6. 在JSX语法中只能使用求值表达式,不能使用语句,所以不能使用if...else...for
  7. 可以使用三元表达式? : <span className={window?`trueClassName`:`falseClassName`}>a标签</span>
  8. ReactDOM.render时不用再写App(),App是函数组件,组件当标签用即可(<App/>),会自动调用App函数。
  9. 可以直接在{}中调用函数。
const GetComponent = (props) => {
  if (props) {
    return <div>你传入了props</div>;
  } else {
    return <div>没有props</div>;
  }
};
//jsx语法
<>
{GetComponent()}
</>
//如果书写<GetComponent/>,React会对函数进行处理,props会被赋值为{},在示例中,props则为true返回<div>你传入了props</div>
  1. 使用短路运算符 && ||
let visibled = false

{visibled && <div>欢迎&&光临</div>}
{!visibled || <div>欢迎||光临</div>}
  1. 只能有一个顶层标签,如果不想输出实际的标签,可以使用空标签<React.Fragment></React.Fragment>或缩写<></>作为最外层元素包裹。

  2. 布尔类型、Null 以及 Undefined 将会忽略。false, null, undefined, true 是合法的子元素。但它们并不会被渲染<div>{null}</div>(UI呈现为空白)。

  3. JSX是可选的(但一般不会直接写React语法,代码可读性及编写效率远不及JSX)
    <a href="https://facebook.github.io/react/">Hello!</a>
    以下为对应不使用JSX写法:
    React.createElement('a', {href: 'https://facebook.github.io/react/'}, 'Hello!')

  4. 内联样式接受的JavaScript对象值若为数字,则会自动添加默认单位(px)。如果要使用em或者是rem其他单位,则需要使用字符串。{top:2,left:`${2}em`,right:'10rem',bottom:"11px"}

  5. dangerouslySetInnerHTML 是 React 为浏览器 DOM 提供 innerHTML 的替换方案。通常来讲,使用代码直接设置 HTML 存在风险,因为很容易无意中使用户暴露于跨站脚本(XSS)的攻击。因此,你可以直接在 React 中设置 HTML,但当你想设置 dangerouslySetInnerHTML 时,需要向其传递包含 key 为 __html 的对象,以此来警示你。例如:

function MyComponent() {
  return <div dangerouslySetInnerHTML={{__html: '<p>hi</p>'}} />
}
  1. 在JSX中只有属性名称但没有填写具体值的会被设为true,例如以下第一个 input 标签 disabled 虽然没设值,但结果和下面的 input 为相同:
<input type="button" disabled />;
<input type="button" disabled={true} />
  1. 自定义属性。 若是希望使用自定义属性,可以使用 data-
<MyComponent data-attr="test" />
  1. 展开语法。在 ES6 中使用 ...是迭代对象的意思,可以把所有对象对应的值迭代出来设定属性,但要注意后面设定的属性会盖掉前面相同属性。
var props = {
  style: {"width":"20px"},
  className: "main",
  value: "yo",  
}
//value在后,则会覆盖掉props对象的value属性
<div {...props} value="str" />
  1. JSX不能直接渲染对象,否则会报错。确实想输出对象可以通过JSON.stringify()转为字符串输出。
  2. JSX允许在模板中插入数组,数组会自动展开所有成员,前提是数组项不能为普通JavaScript对象,但可以是React元素。
(上面只列出了我个人日常开发注意的点,可在搜索引擎搜索React JSX查看更多写法)

React元素

JSX写法最终都会通过babel被编译为React.createElement形式执行(参考注意事项13),这也是React组件中即使代码中没有显式调用过React,但却需要引入import React from 'react'的原因。React.createElement的返回值ReactElement可以代表一个div,但ReactElement并不是真正的div(DOM对象),所以一般称ReactElement为虚拟DOM元素。返回ReactElement的函数,也可以代表一个div,这个函数可以多次执行,每次得到最新的虚拟div,React会对比两个虚拟DOM找出不同,局部更新视图,找不同的算法叫做DOM Diff算法。


函数组件与class组件,此处主要介绍class组件

无状态组件可以用纯函数式组件(React16.8新出的Hook API可模拟class组件的特性,实现管理自身状态及生命周期),此处不深入讲解。

//函数组件就是一个纯函数
function MyFnComponent(props){
    return (<div>{props.name}</div>)
}
//需要注意的是,上述的函数组件,如果用标签调用并且不传入props,props为空对象;用函数的用法不传入props则为undefined。

有状态组件用 ES6的class写法

下面是一个基本的class组件写法
//class实际是原型的语法糖,Prototype,__proto__,继承等JavaScript知识不多赘述
//class组件需要继承React.Component
//背关键字(extends,constructor,super)
export default class MyClassComponent extends React.Component {
  constructor(props) {
    super(props) //这一步不可少,调用父类的构造函数
    this.state = {
      data: [1, 2, 3, 4] //初始化自身可管理的状态
    }
  }
  
  //xxFn = ()=> {}这种写法为es6新语法,会自动绑定this
  addOne = () => {
    let { data } = this.state
    this.setState({ data: [...data, data.length + 1] }) //解构赋值写法
    // this.setState({ data: data.concat([data.length+1]) }) //用数组提供的Api concat同等效果
  }
  
  render() {
    let { data } = this.state;
    return (
      <>
        <div>{data}</div>
        {/* 数组可直接渲染合法项 */}
        <button onClick={this.addOne}>点我数组加一项</button>
      </>
    )
  }
}

Props外部数据

当 React 元素为用户自定义组件时,它会将 JSX 所接收的属性(attributes)以及子组件(children)转换为单个对象传递给组件,这个对象被称之为 “props”。

初始化props,外部数据由父组件提供

class Child extends React.Component {
    constructor(props) {
        super(props) //将props传给super后,this.props就是外部数据对象的地址
    }
    render(){}
}

读取props

const onClick = ()=>{ console.log('u click me') }
<Child name='JackMa' onClick={onClick}>
    <div>i am children</div>
</Child>
//父组件
//----------------

class Child extends React.Component {
    constructor(props) {
        super(props);
    }
    render(){
        return (
        <div onClick={this.props.onClick}> //点击事件为打印u click me
            {this.props.name} //输出JackMa
            <div>
                {this.props.children} //<div>i am children</div>
                //this.props.children包含组件的开始标签和结束标签之间的内容
            </div>
        </div>
        )
    }
}
不要尝试修改props,保持props的只读性,外部数据就要由外部更新,确实需要修改则可以把修改props的方法当作props传进来供子组件使用,本质还是调用父组件的修改数据方法。

state && setState 内部数据

初始化state,外部数据由父组件提供

class Child extends React.Component {
    constructor(props) {
        super(props) 
        this.state = {
            userInfo : {
                name : 'JackMa',
                age : 18,
                gold :9999,
            }
        }
    }
    render(){}
}

//使用es6写法
class Child extends React.Component {
    state = { //与写在constructor中效果一致,state最终都会绑在实例上
        userInfo : {
            name : 'JackMa',
            age : 18,
            gold :9999,
        }
    }
    render(){}
}

写state,使用setState
setState有两种写法,fn为可选的回调函数,它将在 setState 完成合并并重新渲染组件后执行,多次连续调用setState最终会被合并成一次

  1. setState({key:value},fn)
this.setState({name:'Tony'},()=>{console.log('i execute')})
  1. setState((state,props)=>newState,fn),updater 函数接收的state和porps参数都为最新
this.setState(
      (state, props) => {name:'Tony'}, 
      () => {
        console.log("i execute");
      }
    )
setState是异步的,执行后读取this.state并不是最新的state,会在当前代码运行完后,再去更新this.state从而触发UI更新,使用 componentDidUpdate 或者 setState 的回调函数来读取更新后的state,这两种方式都可以保证在应用更新后触发。
setState会自动将新state和旧state进行一级合并

class组件的生命周期

constructor()

作用:

  1. 初始化props
  2. 初始化state,但不能调用this.setState
  3. 绑定this(band)
class Child extends React.Component {
  constructor(props) {
    super(props)
    this.state = {
        data : [1,2,3]
    }
    this.testFn = this.testFn.bind(this)
  }
  
  testFn(){
      console.log(this)
  }
  
  render(){
      return (
        <button onClick={this.testFn}>click</dutton>
      )
  }
}

子类没有显式声明constructor方法时,执行过程中会自动补上constructor

class Child extends Father {
}

// 等同于 
class Child extends Father {
  constructor(...args) {
    super(...args);
  }
}

记住一个潜规则,constructor写了就必须写super(),否则直接报错。

shouldComponentUpdate()

作用:

  1. 组件每次执行redner前都会执行
  2. 函数返回false表示阻止UI更新
  3. 函数返回true表示允许UI更新
  4. 手动判断组件是否要进行更新,根据实际业务逻辑灵活的设置返回值,避免不必要的UI更新
  5. React.PureComponent内置了此功能,可以替代React.Component
//可以将newState和this.state进行对比,如果有不同则返回true进行更新
shouldComponentUpdate(newProps,newState){ //函数接受两个值,新的props和新的state
    if(newState.key === this.state.key){
        return false
    }else{
        return true
    }
}

render()

作用:渲染视图

render(){
    return (<div>halo,world!</div>)
}

componentDidMount()

作用:

  1. 会在组件挂载后(插入 DOM 树中),执行代码,这些代码依赖DOM
  2. 可以获取DOM元素的信息
  3. 官方推荐在此处发起Ajax请求
  4. 只有首次渲染组件时会执行此钩子
componentDidMount(){
    console.log('我只会执行1次')
    //以下为伪代码
    handelDom() //操作DOM元素
    ajax() //发起ajax请求
}

componentDidUpdate()

作用:

  1. 在视图更新后执行代码
  2. 组件首次挂载时不会执行此钩子
  3. 此处也可以发起Ajax请求
  4. 不要直接调用this.setState,否则会进入死循环,必须包裹在条件判断语句中。
  5. 如果 shouldComponentUpdate() 返回值为 false,则不会调用 componentDidUpdate()。
可接受参数:看官方文档
componentDidUpdate(prevProps, prevState){
    //以下为伪代码
    judgeProps() //获取新的props
    judgeState() //获取新的state
    handelDom() //操作DOM元素
    ajax() //发起ajax请求
}

componentWillUnmount()

作用:

  1. 组件将要被移出页面被销毁之前执行代码
  2. 用法举例,在组件挂载后(监听事件,发起请求,增加定时器),那么就在此钩子取消(监听,定时器,请求)

关于forceUpdate()个人基本不用(官方也不推荐用),此处不赘述,看文档

钩子执行顺序

受控组件 && 受控组件

受控组件

React 的自身状态 state 成为“唯一数据源”。渲染表单的 React 组件还控制着用户输入过程中表单发生的操作。被 React 以这种方式控制取值的表单输入元素就叫做“受控组件”。

class Child extends React.Component {
  constructor(props) {
    super(props);
    this.state = {value: ''}
  }

  handleChange = (event)=>{
    this.setState({value: event.target.value});
  }

  handleSubmit = (event)=> {
    console.log(this.state.value);
    event.preventDefault();
  }

  render() {
    return (
      <form onSubmit={this.handleSubmit}>
        <label>
          名字:
          <input type="text" value={this.state.value} onChange={this.handleChange} />
        </label>
        <input type="submit" value="提交" />
      </form>
    );
  }
}

非受控组件

非受控组件表单数据将交由 DOM 节点来处理,react程序中通过ref得到DOM节点的引用

class Child extends React.Component {
  constructor(props) {
    super(props);
    this.input = React.createRef()
    //在React 16.3版本后,使用此方法来创建ref。将其赋值给一个变量,通过ref挂载在dom节点或组件上,该ref的current属性将能拿到dom节点或组件的实例
  }

  handleSubmit = (event)=> {
    console.log(this.input.current.value);
    event.preventDefault();
  }

  render() {
    return (
      <form onSubmit={this.handleSubmit}>
        <label>
          Name:
          <input type="text" ref={this.input} />
          //<input type="text" ref={ref=>this.input=ref} /> 通过回调函数的形式,也能拿到Dom的引用
          //ref="input" 用字符串绑定,通过this.refs.[字符串]也能拿到Dom引用,官方已不推荐此方式,未来可能会被弃用
        </label>
        <input type="submit" value="Submit" />
      </form>
    );
  }
}

CSS方案

关于React的CSS方案,主流的有行内样式,引入样式,CSS Modules,Styled Components。不推荐使用引入CSS文件的方案(样式重置及自定义全局样式除外),因为样式是全局的,可能会存在样式冲突问题,如有两个CSS文件,其中有样式名重复的情况下,其一就会被覆盖掉。推荐使用Styled Components,与sass和less大部分语法类似,如嵌套和继承。

字符串模板介绍

//先通过npm进行安装
npm i styled-components

一些简单用法介绍

//需要先引入
import styled from 'styled-components'

//创建了一个TestDiv样式组件,它将渲染一个带样式的div标签
//组件名首字母仍需要大写,标签名后面的是字符串模板
const TestDiv = styled.div` 
    background:red;
    //嵌套样式,该样式组件中的p标签都会应用此样式
    p{
        font-size:3em;
    }
    color: ${props => props.color}; //可以通过props获得父组件传入参数,进行赋值等操作
    background: ${props => props.primary ? "palevioletred" : "white"};
    font-size: ${props => props.fontSize ? `${props.fontSize}px` : "10px"};
`

//继承了TestDiv的样式,并在此基础上额外增加样式
const ChildComponent = styled(TestDiv)`      
  color: red;
`

更多用法查看styled-components文档


在React项目中引入图片,import与require遵循的模块化规范不一样

使用import

import Img from './images/1.png'
<img src={Img} alt='' />

使用require

<img src={require("./images/1.png")} alt="">

内联样式

style={{background: `url(${require("./images/1.png")})`}}

import bgImg from './images/1.png'
style={{background: {bgImg}}}

在React项目中发送网络请求

举例一些常用方案

  1. jQuery $.ajax
  2. Fetch API
  3. Axios
  4. Request
  5. SuperAgent
推荐使用Axios,API完善,功能强大

Axios中文文档


React项目的路由方案

使用react-router-dom 官方文档


React主流UI库推荐

使用第三方UI库可以节约开发时间成本,保证规范和唯一性。不管是自己编写的组件还是使用第三方UI库,都无法避免缺陷的存在,尽量选择经过市场考验的库(可以参考项目在Github的Star数)。

  1. Ant Design
  2. react-bootstrp
  3. MATERIAL-UI
  4. React Suite
  5. Elemental UI