一、React基础

版本v16.13.0

单独看react不是mvvm也不是mvc,只是一个view层(但是引入了redux等,并且把控制视图的逻辑写在组件内部,就是mvvm了)。 vue也不是mvvm和mvc,vue只是view层(但是如果引入vuex等,就是mvvm了)。mvvm和mvc都是一个架构模式。vm指的是数据驱动视图(也可以理解为组件,一个拥有状态也需要管理状态的视图),比如vue和react都是单向数据流(vue用v-modal实现双向绑定,react即使是非受控组件也不是双向绑定)

一、React简介

React 是一个用于构建用户界面的 JavaScript 库。React 是单向数据流

React 从诞生之初就是可被逐步采用的,因而你可以按需引入或多或少的 React 特性

script直接引入react,如下

// 通过 CDN 的方式引入 React,建议设置 crossorigin 属性
<script crossorigin src="https://unpkg.com/react@16/umd/react.production.min.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.production.min.js"></script>

ReactDOM.render()函数:用来在指定的节点上面渲染对应的react元素,简单说就是用来更新 UI

// 把<h1>Hello, world!</h1>,绑定到后面获取到的root元素上面。root元素是在
ReactDOM.render(
  <h1>Hello, world!</h1>,
  document.getElementById('root')
);

二、JSX 简介

ReactElement就是react元素,或者说就是react中的虚拟dom。React 通过读取这些react元素,然后使用它们来构建 DOM 以及保持随时更新

(1)JSX,是一个 JavaScript 的语法扩展,jsx也就是react中的一个标签语法(React.createElement()的语法糖),也是react中的一个表达式,用来创建ReactElement;语法样式就是,在js中直接写的类似于html的样式。如下:就是一个jsx语法

// 下面标签语法既不是字符串也不是 HTML,而是jsx
// 由于jsx也是一个表达式,所以可以赋值给变量
const element = <h1>Hello, world!</h1>;

(2)jsx中{}(大括号)的形式来嵌入 JavaScript 表达式。属性中嵌入 JavaScript 表达式时,不要在大括号外面加上引号。还一个重要的是 如果大括号里面为一个数组,则数组里面的每一项都会被渲染为一个reactElement,并且展示出来。注意这种遍历的情况,需要在每一项上面加上key属性(这个key的默认值为数组的索引)

import React from 'react'
import { Router, Route, Link } from 'react-router'
import ReactDOM from 'react-dom'

const name = 'Josh Perez';
const element = <h1>Hello, {name}<img src={user.avatarUrl}></img></h1>; // 后面一段为在属性中插入js表达式,直接用大括号就好了。不需要加上引号

ReactDOM.render(
  element,
  document.getElementById('root')
);

// 大括号里面为一个数组,数组里面的每一项都会被渲染处理
function myComponent() {
    const arr = [11.22];
    return (
    <div>
    {
        arr.map(value => {return <p key={value}>{value}</p>})  // 这里return出来的一个数组,里面的每一项都会被渲染为一个reactElement.而且,每一项是需要有一个key值的
    }
    </div>
    );
}

(3)jsx里面属性的命名更加类似于js,如class 会写为className

const element = (
  <h1 className="greeting">
    Hello, world!
  </h1>
);

(4)React.createElement(component, props, …children)创建ReactElement: 这个实际上是react原生的创建ReactElement的语法。上面的JSX也会被babel-loader或者ts-loader解析成这个函数,然后来创建ReactElement。

// 上面(3)里的代码,和下面这段等效
const element = React.createElement(
  'h1', // 标签
  {className: 'greeting'}, // 属性
  'Hello, world!' // 子元素
);

// 创建的ReactElement的样式为下,注意:这是简化过的结构
const element = {
  type: 'h1',
  props: {
    className: 'greeting',
    children: 'Hello, world!'
  }
};

三、React 元素渲染

元素是构成 React 应用的最小砖块。元素描述了你在屏幕上想看到的内容

与浏览器的 DOM 元素不同,React 元素是创建开销极小的普通对象React DOM这个插件会负责更新 DOM 来与 React 元素保持一致。可以理解为react dom 就是虚拟dom,而React 元素就是虚拟dom中的每个dom节点

React DOM 在更新的时候,会将元素和它的子元素与它们之前的状态进行比较,并只更新和之前状态有差别的部分

四、组件 & Props

props参数:在react中,自定义的组件会将jsx中接收的属性以及子组件(children)转换为单个对象传递给组件,这个对象被称之为 “props”

组件:react中,会将以小写字母开头的标签视为原生 DOM 标签,而大写字母开头标签的则视作一个自定义组件。组件都会返回(render)用于描述当前组件内容的 React 元素

props和state的区别之一:一般情况下props是不可变数据是从外面传入进来的,而state可以任意修改是组件自己内部的。

定义组件: react中的组件有两种定义方式,一种是定义为函数组件,另外一种是定义为类组件

1、无状态组件(函数组件)

  • 无状态组件(函数组件):这个组件的设计目的是,只做为展示性组件,即根据props展示组件,不涉及到要state状态的操作。函数组件接收一个props参数,返回一个reactElement。并且函数组件还有下面几个特点
    • <1>组件不会被实例化,整体渲染性能得到提升
    • <2>组件不能访问this对象。无状态组件由于没有实例化过程,所以无法访问组件this中的对象,所以也没有下面这些this上面的东西了(注,props是直接传入的,所以可以使用props)
    • <3>没有state
    • <4>没有context
    • <5>没有refs
    • <6>没有生命周期。
    • <7>如果函数组件中用了hooks,就也可以使用上面的this、state、context、refs和生命周期。
    • <8>性能。目前React还是会把函数组件在内部转换成类组件,所以使用函数组件和使用类组件在性能上并无大的差异
  • 函数组件有一个特点,就是在每次重新渲染的时候,函数组件会把整个组件里面的所有东西都重新渲染,即把所有的函数那些都重新创建,所以可能会导致React.memo的优化失败
// 函数组件
// test.js文件
function Test(props) {
    const showMessage = (ev) => {
      alert('Followed ' + props.user, ev, this); // ev值为'aaa', this值为undefined
    };
    const handleClick = () => {
      setTimeout(showMessage, 3000); // 这里都是没有用this,而是直接访问的showMessage
    };
    return (
      <div onClick={()=>{showMessage('aaaa')}}>show me about function component2222222222233355!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!</div>
    );
  }
export default Test;

// welcome.tsx文件
import Test from '@/pages/test/test'
export default (): React.ReactNode => (
  <PageHeaderWrapper>
    <Test /> // 使用上面的函数组件,还是和类组件一样的方法
  </PageHeaderWrapper>
);

2、类组件

类组件:类组件中的props存放在this中,这一点和VUE中的props类似,但是Vue可以直接使用this后跟属性名,但是React中还需要this.props后跟相对应的属性名

// 类组件
class ProfilePage extends React.Component {
  showMessage = () => {
    alert('Followed ' + this.props.user);
  };
  handleClick = () => {
    setTimeout(this.showMessage, 3000);
  };
  render() {
    return <button onClick={()=>{this.handleClick()}}>Follow</button>;
  }
}
export default ProfilePage;

3、父子组件传值

  • 父组件给子组件参数传递,props:父组件给子组件传递参数依然是用 props(“props” 是 “properties” 简写);父组件中,写在子组件标签上面的所有属性,都为传到子组件中的参数,然后传递进来的参数(如果是类组件)在render方法里面,通过this.props来调用。this.props上面的属性名字,就为传进来的名字,比如上面的this.props.name
    • Props 的只读性: 组件无论是使用函数声明还是通过 class 声明,都决不能修改自身的 props
    • JSX 组件标签中的所有内容都会作为一个 prop属性上的children 传递给对应的组件。this.props.children: 这个东西很好用,相当于vue中的router-view,用来替换写在父组件中的子组件,router 会帮我们找到这个 children
// BasicLayout.tsx
// 下面的this.props.children就是在路由BasicLayout下面的子路由的位置
  <Layout>
      <Header></Header>
      <Content style={
   
   {height: this.state.contentHeight,padding: '15px'}}>
        {this.props.children}
      </Content>
  </Layout>
  • 子组件给父组件传递参数,触发自定义事件:在react中,也是通过触发自定义事件来使子组件给父组件传递参数。不过好像是不能用$emit这种来触发父组件中的自定义事件的。react中传参如下:
    • 父组件中:在子组件的标签中绑定一个自定义事件,事件里面绑定一个写在父组件中和render同级的事件处理函数。在 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> {
    handleSquareClick(index: any) { // 父组件中定义的自定义事件的处理函数
      console.log(index);
    }
    renderSquare(i: any) {
      return <Square index={i} value={this.state.squares[i]} onSquareClick={(index:any) => this.handleSquareClick(index)}/>; // 父组件写给子组件的props
    }
  
    render() {
      return (
        <div>
          <div className="board-row">
            {this.renderSquare(0)}
          </div>
        </div>
      );
    }
  }

五、State & 生命周期

state和生命周期都是只有class组件才有,当然this.setState()也是只有class组件才有的

1、state

State 与 vue中的data 类似,可以通过改变这个值来改变界面;state 是私有的,并且完全受控于当前组件

  • 组件的state
    • 组件内部数据state: 每个组件中可以有state,用来存当前组件的数据,且应该被视为一个组件的私有属性。在组件的构造函数中初始化,其余地方用this.state来访问
    • this.setState(updater, callback): 这个为修改组件中的state,this.setState(),传入的值为要修改的属性的一个对象,或者传入一个函数。如果传入对象的话,传入的对象会和state上面的值进行合并;传入函数的话。函数的返回值会和state上面的值合并。如果不用这个函数改变state,而是直接给this.state赋值,则不会有效果,但是如果直接给this.state里面的属性赋值,那么会改变对应的属性,但是不会同步更新到dom里面
    • 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>
      );
    }
  }

2、生命周期

和vue一样,react也有一个生命周期。生命周期分为三个阶段,分别是挂载阶段、更新阶段、卸载阶段。详情请见,零、React API章节的React.Component

// 挂载阶段: 当组件实例被创建并插入 DOM 中时
constructor(props)
static getDerivedStateFromProps(nextProps, prevState)
render()
componentDidMount()
// 更新阶段: 当组件的 props 或 state 发生变化时会触发更新
static getDerivedStateFromProps(nextProps, prevState)
shouldComponentUpdate(nextProps, nextState)
render()
getSnapshotBeforeUpdate(prevProps, prevState)
componentDidUpdate(prevProps, prevState, snapshot)
// 卸载阶段: 当组件从 DOM 中移除时会调用如下方法
componentWillUnmount()

六、事件处理

React 自定义事件命名为 on[Event],将事件处理函数命名为 handle[Event] 这样的格式。小驼峰式(camelCase),而不是纯小写

  • <1>保证事件处理函数中this可使用。有下面三种方法
    • (1)直接在构造函数中给函数绑定this。这种方法是指,普通方法传参的时候,直接在构造函数中绑定,这方法太麻烦了,不用这个
    • (2)使用实验阶段的 public class fields 语法,即在定义函数的时候,把函数定义为箭头函数,那么this就自动是整个class组件了。这个方法最好,不过就是在实验阶段。如果不生效,可能就需要安装插件了。npm install --save-dev babel-plugin-transform-class-properties,配置为{"plugins": [["babel-plugin-transform-class-properties", { "spec": true }]]}。配置了也不一定有效,所以还是函数组件好
    • (3)调用函数的时候使用箭头函数,这个方法还行,不过会导致PureComponent的优化无效(原因是调用的时候的箭头函数,创建的是匿名函数,而匿名函数的地址每次都不一样,所以nextProps和preProps就会不一样),如下
  • <2>向事件处理函数中对应的传参
    • (1)使用bind进行传参。由于bind是把参数传入,并且是返回一个函数的,所以不会影响事件处理函数
    • (2)使用箭头函数进行传参
// 使用箭头函数来让事件处理函数中this可使用
class LoggingButton extends React.Component {

  constructor(props) {
    super(props);
    this.handleClick = this.handleClick1.bind(this); // 法1,直接在构造函数里面绑定,这时候,函数里面的this就是class了,如果不绑定,则this为undefined
  }
  
  handleClick1() { // 法一的函数
    console.log('this is:', this);
  }
  handleClick2 = () => {  // 法二的函数,直接这样定义就好了。里面的this就是整个组件了。一般都是这样用
    console.log('this is:', this);
  }
  handleClick3() { // 法三的函数
    console.log('this is:', this);
  }

  render() {
    return (
      <button onClick={this.handleClick1}> // 法一,直接在构造函数里面绑定
        Click me1
      </button>
      <button onClick={this.handleClick2}> // 法二,定义的函数为箭头函数。一般都是用这个
        Click me2
      </button>
      <button onClick={(e) => this.handleClick(e)}> // 法三,在调用的时候使用箭头函数
        Click me3
      </button>
    );
  }
}

// 使用bind进行传参。这样把对应的this也改变了,就可以用上面那个法二的语法了。一般都是用这个
<div onClick={this.func.bind(this, 'shommmmmmmmmmmmm')}>1111111</div>
// 使用箭头函数进行传参
<div onClick={(ev) => {this.func(ev, '111111')}}>1111111</div>

七、条件渲染

就是相当于实现vue种的v-if

  • 用运算符来实现vue中的v-if这种条件渲染效果
    • 用与运算符 &&来实现。这个一般是用来实现那种只有一种结果的情况
    • 用三目运算符 ? :来实现。这个就可以实现两种结果了。
  • 如果组件里面的render 方法返回 null,则不进行任何渲染。但是不会影响组件的生命周期,组件的生命周期函数依然会被调用
// 下面为用与运算符 ``&&``来实现的。unreadMessages.length > 0为true的时候,就返回后面的
return (
    <div>
      <h1>Hello!</h1>
      {unreadMessages.length > 0 &&
        <h2>
          You have {unreadMessages.length} unread messages.
        </h2>
      }
    </div>
  );
  
 // 下面为用三目运算符 ``? :``来实现
  return (
    <div>
      {isLoggedIn ? (
        <LogoutButton onClick={this.handleLogoutClick} />
      ) : (
        <LoginButton onClick={this.handleLoginClick} />
      )}
    </div>
  );
  
  // 下面这个组件,return为null,不进行任何渲染
  function myComponent(props) {
      return null;
  }
  

八、列表 & Key

就是相当于实现vue中的v-for,就是一个遍历

  • 用map来实现vue中的v-for这种遍历效果:实际上在react中的做法是,给数组使用map,然后把最后生成好了的完整数组dom结构,插入到render里面去。这里的原理是在jsx的大括号里面如果包裹数组,那么这个数组的每一项都会被渲染为一个reactElement
  • key:key 帮助 React 识别哪些元素改变了,比如被添加或删除。所以如果是在jsx中使用数组进行遍历的情况,需要给数组的每一项一个key值(这个key的默认值为数组的索引)
    • 数组元素中使用的 key 在其兄弟节点之间应该是独一无二的。然而,它们不需要是全局唯一的
    • 这里这个key,最主要的目的就算性能优化;因为在diff算法中,新旧节点树的同一位置的兄弟节点中,如果有相同key的节点,有可能是只需要移动位置,而不需要创建
    • 还有一个重要的点是,就算在组件上, 指定了key值,但是在这个组件的props属性中,依然是无法读取出key的,即无法访问props.key
// 创建一个变量,然后使用
function NumberList(props) {
  const numbers = [1, 2, 3, 4, 5];
  const listItems = numbers.map((number) =>
    <li key={number.toString()}>{number}</li>
  );
  return (
    <ul>{listItems}</ul>
  );
}
// 或者用下面方法,直接写在大括号里面
function NumberList(props) {
  const numbers = props.numbers;
  return (
    <ul>
      {numbers.map((number) =><ListItem key={number.toString()} value={number} />)}
    </ul>
  );
}

九、表单

就是相当于实现vue中的双向绑定。

  • 受控组件与非受控组件:它们都是基于react的form表单组件元素的
    • 受控组件:就是为某个form表单组件添加了value属性。而一旦设置了一个value值(这个值是存在state里面的),则不管用户在界面输入什么东西,值都不会被改变,所以想要改变这个value就需要我们自己来调用事件来改变(setState),所以称为受控;如:<input type="text" value="Hello!" />
      • 受控组件如果不需要默认值,而是需要把组件的placeholder展示出来,则需要把value设置为undefined或者null
    • 非受控组件:就是没有添加value属性的表单组件元素。这时候,不管用户输入什么(值是存在DOM 节点上面的),都是会显示到界面上的。如<input type="text" />或者<input type="file" />当type等于file的时候,永远不可能绑定value,所以这个肯定就是非受控组件了。
      • 非受控组件的使用方法为给组件绑定ref,然后或者这个ref,就可以获取到组件的值了。具体使用请看进阶部分
  • 通过自己绑定state来实现vue中的双向绑定效果:具体做法就是给表单的value绑定为state,然后给表单绑定change事件,事件中用setState()来改变state
    • 下面绑定在jsx上面的这个onChange实际上是js中的input事件,因为这里的onChange是在value改变的时候就立即触发,而不是等到失去焦点的时候才触发。在jsx里面使用onInput会报错
    • label标签上面的for,在react中应该被写为htmlFor。如:<label htmlFor="namedInput">Name:</label>
  • 关于textarea标签:在html中textarea标签上面是没有value属性的,但是可以通过js中的textarea的dom元素来读取value值(读取的value值为textarea里面的文本值)。而在 React 中, 标签拥有了value属性,来表示内容
  • 关于select标签的选项选中:在react中,在select 标签上使用 value 属性,来对应option 的value值,来进行选中
class Board extends React.Component<any, any>  {
  constructor(props: any) {
    super(props);
    this.state = {value: '',value2: 'lime'};
    this.myRef = React.createRef();
  }
  handleChange(ev: any) {this.setState({value: ev.target.value});}
  handleChange2(ev: any) {this.setState({value2: ev.target.value});}
  show() {this.myRef.current.value;} // 这里为使用非受控组件,直接获取对应ref上面的值
  render() {
    return (
          <input type="text" value={this.state.value} onChange={(ev) => {this.handleChange(ev)}} />
          <select value={this.state.value2} onChange={(ev) => {this.handleChange2(ev)}}>
            <option value="grapefruit">葡萄柚</option>
            <option value="lime">酸橙</option>
          </select>
          <input type="text" value={undefined} placeholder='gogogo' /> // 这里把value设置为了undefined,所以可以显示出对应的placeholder
          <input type="text" detaultValue={'11'} ref={this.myRef} /> // 这里为使用非受控组件,直接给组件绑定ref
    );
  }
}

十、状态提升

状态提升,就是把子组件中相同的数据或者方法,放到父组件中去。使用context应该也是一种状态提升的方法。

十一、组合 vs 继承

组合即在组件中传入一些内容,然后使用props.children来获取。或者使用render props来在属性中传入jsx

react中的组件没有继承这一需求。一般使用组合就好了

function FancyBorder(props) {
  return (
    <div className={'FancyBorder FancyBorder-' + props.color}>
      {props.children} 
    </div>
  );
}
function WelcomeDialog() {
  return (
    <FancyBorder color="blue">
      <h1 className="Dialog-title">Welcome</h1>
    </FancyBorder>
  );
}

猜你喜欢

转载自blog.csdn.net/rocktanga/article/details/121348170
今日推荐