① React

React

目录

  • DOM diff 算法:所有的 DOM 变动,都先在虚拟 DOM 上发生,然后再将实际发生变动的部分反映在真实 DOM 上(可以极大提高网页的性能)

React 是一个视图层框架,而 vue 是一个完整的 MVVM 框架

  • 特点
    1. 声明式设计
    2. 高性能 -- virtualDOM
    3. 组件化开发
    4. 单向响应的数据流
    5. jsx 扩展

1 使用

1.1 React 依赖 -- react & react-dom

//React在每个组件中都必须引入,哪怕你使用React变量
import React from 'react';

1.2 节点渲染与创建

1.2.1 React 渲染

  • React 构建的应用通常只有一个根DOM节点

要将一个 React 元素渲染到根 DOM 节点中,需要将其传入ReactDOM.render()

  • ReactDOM.render(template, targetDOM)

是 React 的最基本方法,用于将模板转为 HTML 语言,并插入指定的 DOM 节点

  • template:可以是 HTM L标签或 React 组件
  • targetDOM:挂载点,必须为元素节点
const element = <h1>Hello, world</h1>
ReactDOM.render(element, document.getElementById('root'))

1.2.2 热更新

热更新 -- 自动刷新浏览器

  • 原理:websocket
1. 更新已渲染的元素
  • React 元素是 不可变对象。一旦创建就无法更改其子元素或属性

  • 更新 UI 唯一的方式就是创建一个全新的元素,并将其传入 ReactDOM.render()

function tick() {
    const element = <h1>Hello, world</h1>
    ReactDOM.render(element, document.getElementById('root'))
} 
setInterval(tick, 1000)

tip: 大多数 React 应用只会调用一次 ReactDOM.render()

2. React 只更新它需要更新的部分
  • ReactDOM 会将元素和它的子元素与它们之前的状态进行比较,并只会进行必要的更新来使 DOM 达到预期的状态

1.3 JSX

jsx 实现在 js 文件中编写 html 代码
因为 js 代码与 JSX 代码并不兼容,凡是使用 JSX 的 script 标签都需要加上 type="text/babel"

tip: JSX 不是字符串

  • 在 js 中编写 HTML 代码,在编译时 webpack 会通过特定的规则(loader)编译 JSX 代码到 js

JSX 不是必须的,它只是 React.createElement(type,[props],[...children]) 的语法糖,在我们应用 JSX 进行开发的时候,其实它最终会转化成 React.createElement… 去创建元素

ReactDOM.render(
  // JSX写法
  <div id="container" className="home">
    { //<h1>Hello {username}</h1> }
    <ul>
      <li>data1</li>
      <li>data2</li>
    </ul>
    <label htmlFor="username" style={ myStyle }>用户名:</label>
    <input id="username" autoFocus onKeyDown={ ()=>console.log('keydown') }/>
  </div>,
  document.getElementById('root')
)

1.3.1 JSX 如何分辨 js、html 代码?

  • 编译规则
    • 尖括号内以 html 代码规则编译
    • 花括号内以 js 代码规则编译

可以在 js 中编写 html 代码,在 html 中编写 js 代码要使用 {}

  • JSX 的写法
    • 关键字不能直接使用
      • class -> className
      • for -> htmlFor
    • 多个单词的属性必须使用驼峰形式
      • autofocus -> autoFocus
      • onkeyup -> onKeyUp
    • 必须闭合标签
    • {} 只能使用 js 表达式
      • style 属性值必须是一个对象
      • {} 内不允许出现 let、var、const 等关键字
    • 注释
在 JSX 中嵌入表达式
  • 可以在 花括号 内放置任何有效的 js 表达式
const name = 'Josh Perez';
const element = <h1>Hello, { name }</h1>

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

1.3.2 JSX 也是一个表达式

  • 编译后,JSX 表达式也会被转为 js 函数调用,并且得到 js 对象
function formatName(user) {
	return user.firstName + ' ' + user.lastName;
}
function getGreeting(user) {
	if(user) {
		return <h1>Hello, { formatName(user) }!</h1>
	}
	return <h1>Hello, Stranger.</h1>
}

1.3.3 JSX 特定属性

1. 引号 -> 将属性值指定为字符串字面量
const element = <div tabIndex="0"></div>
2. 花括号 -> 在属性值中插入一个 js 表达式
const element = <img src={ user.avatarUrl } />
3. 不可同时使用 引号花括号
4. 警告
  • JSX 语法更接近 js 而不是 html,所以 ReactDOM 使用 驼峰命名 来定义属性名

class -> className tabindex -> tabIndex

1.3.4 JSX 防止注入攻击

  • ReactDOM 在渲染前会进行转义,有效防止 XSS 攻击
const title = res.pot
// 直接使用是安全的
const element = <h1>{ title }</h1>

2 面向组件编程

2.1 组件定义

  • 面向组件编程

在开发过程中,要善于观察和抽象。尤其是在项目前期,不要着急写代码,一定要观察项目的原型图或者设计稿,弄懂哪些部分是可以拆分成复用的公共组件的。这样做能让你后面的工作,事半功倍

2.1.1 规范

  • 组件必须以 首字母大写 开头
  • 只能有 唯一的顶级标签

2.1.2 分类

  • react 组件:接收唯一带有数据的 props 对象与返回一个 react元素

写入组件的方法会成为原型方法

1. 函数组件(无状态组件、受控组件、UI组件)

纯展示组件,这种组件只负责根据外部传入的 props 来展示,书写更简洁,执行效率更高

  • 利用函数来定义一个组件,函数必须有返回值

  • 特点

    • 只根据传入的 props 属性展示不同的UI效果
    • 组件不会被实例化,整体渲染性能得到提升--在实际开发过程中尽量使用函数组件
    • 组件不能访问 this 对象
    • 组件无法访问生命周期的方法
//定义
function MyComponent(props){
  return <h1>函数组件</h1>
}

//使用
ReactDOM.render(
  <MyComponent myname="zmoon" />,
  document.getElementById('app')
);
2. 类组件(有状态组件、非受控组件、容器组件)

类继承组件有更丰富的特性(state状态、生命周期等)

  • 利用 class 类创建的组件,继承自 React.Component
  • 在类组件中可以使用 this生命周期 等特性
  • 只有在 renderconstructor生命周期函数 中才有 this 指向,其他自定义的方法默认没有 this 指向
  • 修改状态:重写一份数据并覆盖原来的数据
    • this.setState( )
  • 改变函数 this 指向
    • fn.bind 改变函数 fn 的 this 指向,并返回一个新的函数,多次调用只有第一次生效
class TodoForm extends React.Component{
  constructor(props){
    super(props) // 使得 this.props 在其他自定义方法中也可以使用 否则只有 render 函数内可以使用
    this.state = { keyword: '' }
  }
  render(){
    return (
      <div>
        <input type="text" value={ this.state.keyword }/>
        <button>添加</button>
      </div>
    )
  }
}
3. 受控组件 & 非受控组件
  • 对于受控组件来说,输入的值始终由 React 的 state 驱动
  • 非受控组件,使用 ref 来从 dom 节点中获取表单数据,而不是为每个状态更新都编写数据处理函数

2.2 组件通讯 props

  • props 获取方式
    1. 函数组件:通过参数 props 访问, 组件的第一个参数
    2. 类组件:通过 this.props 访问

tip: props 是一个对象,包含使用组件时的所有属性,属性必须为只读的
tip: 所有 React 组件都必须像纯函数一样保护它们的 props 不被更改

2.2.1 组件通讯 -- 父传子 props

  1. 父组件设置为子组件的属性值,并传递数据
  2. 子组件通过 props 使用

index.js

render(){
  return (
    <div>
      <TodoForm addItem={ this.addItem } />
      <TodoContent />
    </div>
  )
}

TodoForm.js

add() {
  // 子组件内触发父组件传递的方法
  this.props.addItem(this.state.keyword);
  this.setState({
    keyword: ""
  })
  this.keyword.focus()
}

2.2.2 深层次组件通讯 -- context 组件共享

  • 逐层传递:操作繁琐、很难维护
  • context 组件共享

所谓 context, 就是上下文环境, 某个组件只要往自己的 context 里面放了某些状态, 这个组件之下的所有子组件都可以直接访问这个状态(context 好比组件的全局变量,能让所有子组件直接访问)

1. 创建 context
  • const MyContext = React.createContext('default')

MyContext.js

import React from 'react';

//default为默认值,当父组件不提供Provider时,使用默认值
const MyContext = React.createContext('default');

export default MyContext;
2. 父组件往 context 中写入共享数据 -- Provider
  • <MyContext.Provider value = {}></MyContext.Provider>
render(){
  let { deleteItem, selectItem, completeItem } = this;
  return (
    <div>
      <MyContext.Provider value={{ deleteItem, selectItem, completeItem }}>
        { /**...**/ }
      </MyContext.Provider>
    </div>
  )
}
3. 子组件使用 context
  1. 类组件:contextType -- 仅类组件可用
SubComponent.contextType = MyContext;
render(){
  return <Button theme={ this.context }>
}
  1. 函数组件:Consumer -- 通用
  • <MyContext.Consumer>{ value => { /*基于context进行渲染*/ }}</MyContext.Consumer>
return (
  <MyContext.Consumer>
    {
      value => {
        let { deleteItem, selectItem, completeItem } = value;
        return /* 基于 context 值进行渲染*/
      }
    }
  </MyContext.Consumer>
)

2.3 组件状态 state

React 会在 state 状态改变后自动执行组件中的 render 方法渲染视图

2.3.1 初始状态

class MyComponent extends React.Component {
  constructor() {
    super() // 这行代码不能少
    this.state = {
      isLiked: false
    }
  }
}

2.3.2 修改状态 setState

1. 格式:setState(nextState [,callback])
  • nextState: 将要设置的新状态,该状态会和当前的 state 合并
  • callback: 可选参数,回调函数。该函数会在 setState 设置成功,且组件重新渲染后调用
2. 格式:setState(fn [,callback])

依赖上次 setState 的结果

  • fn(prevState)
this.setState(prevState => {
  return { num: prevState.num+1 }
})
3. 多次 setState() 合并
  • React 内部自动进行对比,得到最终结果后才渲染视图,所以并不需要担心多次进行 setState 会带来性能问题

PS:调用 setState() 并不会马上修改 state, 而是进入到一个更新队列里面,所以不能在组件内部通过 this.state.xx = xx 直接修改状态,因为修改后会被队列中的 setState() 替换(如下两次输出都为false)

console.log(this.state.isLiked); // false
this.setState({
  isLiked: true
});
console.log(this.state.checked); //false
4. 强制更新组件: forceUpdate(callback)
  • forceUpdate() 方法会使组件调用自身的 render() 方法重新渲染组件,组件的子组件也会调用自己的 render(),一般来说,应该尽量避免使用 forceUpdate(),而仅从 this.propsthis.state 中读取状态并由 React 触发 render() 调用

2.3.3 state VS props

  • 一个组件的 state 中的数据可以通过 props 传给子组件
  • 一个组件可以使用外部传入的 props 来初始化自己的 state
  • state 是让组件控制自己的状态,props 是让外部对组件自己进行配置

2.3.4 正确使用 State

1. 不要直接修改 state
  • 构造函数是唯一可以给 this.state 赋值的地方
// wrong
this.state.commit = 'Hello'
// Correct
this.setState({ commit: 'Hello' })
2. State 的更新可能是异步的
  • 出于性能考虑,react 可能会把多个 setState() 调用合并成一个调用

  • 要解决异步调用的问题,可以让 setState() 接收一个函数而不是一个对象,这个函数用上一个 state 作为第一个参数,将此次更新被应用时的 props 作为第二个参数

// wrong
this.setState({
  counter: this.state.counter + this.props.increment
})
// Correct
this.setState((state, props) => {
  counter: state.counter + props.increment
})
3. State 的更新会被合并
4. 数据是向下流动的
  • 任何的 state 总是所属于特定的组件,而且从该 state 派生的任何数据或 UI 只能影响树中“低于”它们的组件

2.4 高阶组件 HOC

  • 高阶组件是一个纯函数

    不依赖执行上下文,也不影响上下文的变量,输出只由输入决定

  • 高阶组件的参数为组件,返回值为新组件
  • 高阶组件是一种设计模式,类似于装饰器模式

2.4.1 使用场景

  • 操纵 props
  • 通过 ref 访问组件实例
  • 组件状态提升
  • 用其他元素包装组件

2.4.2 使用

1. 定义一个 HOC
//utils/withStorage.js
import React, { Component } from 'react'
const withStorage = WrappedComponent => {
  return class extends Component{
    componentWillMount() {
      let data = localStorage.getItem('data')
      this.setState({ data })
    }

    render() {
      return <WrappedComponent data={this.state.data} {...this.props} /> 
    }
  }
}
export default withStorage
2. 在组件中使用 HOC
//components/Home.js
import React, { Component } from 'react'
import withStorage from '@/utils/withStorage'

class Home extends Component{
  render() {
    //通过高阶组件可以直接获取data
    return <h2>{this.props.data}</h2>
  }
}
export default withStorage(Home)
3. ES7@装饰器写法
import React, { Component } from 'react'
import withStorage from '@/utils/withStorage'

@withStorage
class Home extends Component{
  render() {
    return <h2>{this.props.data}</h2>
  }
}

export default Home

PS:需安装 @babel/plugin-proposal-decorators 插件

3 生命周期

生命周期:组件从创建到销毁的过程

3.1 阶段

针对类组件 函数组件没有生命周期

1. Initial 初始化阶段 -- 实例化

  • constructor

2. Mounting 阶段 -- 已插入真实 DOM

  • componentWillMount -- 不推荐使用
  • componentDidMount -- ajax

3. Updating 阶段 -- 重新渲染

  • componentWillUpdate -- 不推荐使用

  • componentDidUpdate -- 慎重使用

    判断值修改时才发起 ajax 请求

componentWillUpdate(nextProps, nextState) {
  //nextState: 将要更新的值
  //this.state:当前的值
  console.log('componentWillUpdate', nextProps, nextState, this.state);
}
componentDidUpdate(prevProps, prevState) {
  console.log('componentDidUpdate', prevProps, prevState, this.state);
  //判断值有修改之后才发起ajax请求
  if(prevProps.username !== this.props.username){
    console.log(123);
    //发起ajax请求
    //在此修改state要慎重,避免死循环
  }
}
组件在什么情况下会被刷新?
  • props 更新
  • state 更新
  • 强制刷新:this.forceUpdate()

4. Unmounting 阶段 -- 已移除真实 DOM

  • componentWillUnmount -- 清除定时器 取消ajax请求

3.2 特殊的生命周期函数 -- 父组件更新会导致子组件更新

  • componentWillReceiveProps -- 不推荐使用
// 组件接收到了新的属性。新的属性会通过 nextProps 获取到。
// 组件没有收到新的属性,但是由于父组件重新渲染导致当前组件也被重新渲染。
componentWillReceiveProps(nextProps) {
  console.log('componentWillReceiveProps', nextProps);
}

3.3 怎么阻止父组件的更新导致子组件也更新?

  • shouldComponentUpdate

条件生命周期,默认返回值 true, true 则渲染
一般用作性能优化 -- 不跑 render 则优化性能

  • 慎重在 componentDidUpdate shouldComponentUpdate 里面使用 this.setState, 会导致死循环
    ---> 把 this.setState 放入条件判断语句
//节省开销,提高系统的性能
shouldComponentUpdate(nextProps, nextState) {
  if(nextProps.age === this.props.age){
    return false;
  }
  return true;
}

4 事件处理

  • React 事件的命名采用 小驼峰式 而不是纯小写
  • 使用 JSX 语法时需要传入一个 函数 作为事件处理函数而不是一个字符串
  • JSX 不能通过返回 false 阻止默认行为,必需显式使用 preventDefault

4.1 event 对象与事件处理函数传参

1. event 对象的获取

  • 默认事件处理函数的最后一个参数

TodoForm.js

<input type="text" 
  value={ this.state.keyword }
  onChange={ this.changekeyWord } 
/>
changekeyWord(e) {
  this.setState({
    keyword : e.target.value
  })
}

2. 传参

  • bind
<button onClick={ completeItem.bind(this, item.id) }>完成</button>

3. 使用箭头函数调用

  • event对象需要手动传递
  • 可以传递其他参数
//定义
clickHandler(e,num){
  console.log(e,num);
}

//使用
<button onClick={ e => this.clickHandler(e, 10) }>按钮</button>

4.2 this 指向的改变

  <button onClick={ this.clickHandler }>按钮</button>

以上 clickHandler 被调用时,内部的 this 不指向组件实例,也不指向 button 元素,而是得到 undefined,如果需要用到 this 需要使用以下方式改变 this 指向

1. bind

  • 初始化时
  • 绑定事件时

2. 使用箭头函数

  • 定义时
  • 绑定事件时

5 条件渲染

1. 元素变量

  • 使用变量来储存元素,有条件地渲染组件,而其他的渲染部分并不会因此而改变
function Greeting(props) {
  const isLoggedIn = props.isLoggedIn;
  if (isLoggedIn) {
    return <UserGreeting />;
  }
  return <GuestGreeting />;
}

ReactDOM.render(
  <Greeting isLoggedIn={ false } />,
  document.getElementById('root')
);

2. 与运算符 &&

function Mailbox(props) {
  const unreadMessages = props.unreadMessages;
  return (
    <div>
      <h1>Hello!</h1>
      { unreadMessages.length > 0 &&
        <h2>
          You have { unreadMessages.length } unread messages.
        </h2>
      }
    </div>
  );
}

const messages = ['React', 'Re: React', 'Re:Re: React'];
ReactDOM.render(
  <Mailbox unreadMessages={messages} />,
  document.getElementById('root')
)

3. 三目运算符

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

6 列表渲染

  • 在 JSX 中不能直接渲染对象,但可以渲染数组,一般列表渲染都是使用 map 方法

TodoContent.js

<tbody>
  {
    datalist.map((item, index) => {
      return <TodoItem
        key={ item.id }
        item={ item }
        index={ index }
        completeItem={ completeItem }
        deleteItem={ deleteItem }
        selectItem={ selectItem }
      />
    })
  }
</tbody>

用 key 提取组件

  • 元素的 key 只有放在就近的数组上下文中才有意义
function ListItem(props) {
  // 正确!这里不需要指定 key:
  return <li>{props.value}</li>;
}

function NumberList(props) {
  const numbers = props.numbers;
  const listItems = numbers.map(number =>
    // 正确!key 应该在数组的上下文中被指定
    <ListItem key={ number.toString() } value={ number } />
  );
  return (
    <ul>
      {listItems}
    </ul>
  );
}

const numbers = [1, 2, 3, 4, 5];
ReactDOM.render(
  <NumberList numbers={ numbers } />,
  document.getElementById('root')
);
  • key 只是在兄弟节点之间必须唯一

key 会传递信息给 React ,但不会传递给你的组件

const content = posts.map((post) =>
  <Post
    key={ post.id }
    id={ post.id }
    title={ post.title } />
);
// Post 组件可以读出 props.id,但是不能读出 props.key

7 获取真实DOM节点 refs

7.1 应用位置

  • 应用在元素节点上:对节点的引用
  • 应用在组件上:对组件实例的引用

函数组件不可使用 ref

7.2 适合使用 refs 的情况

  • 管理焦点,文本选择或媒体播放
  • 触发强制动画
  • 集成第三方 DOM 库

7.3 设置方式

  • React.createRef()
  • 回调 Refs

ref={ el => this.myRef=el }

// React.createRef()
this.btnSave = React.createRef();
<button ref={ this.btnSave }>保存</button>
//获取节点
this.btnSave.current

//回调 Refs
<button ref={el => { this.btnSave = el }}>保存</button>
// 获取节点
this.btnSave

8 组合 vs 继承

8.1 包含关系

1. 多数情况下

使用 children prop 来将子组件传递渲染
function FancyBorder(props) {
  return (
    <div className={ props.color }>
      { props.children }
    </div>
  )
}
使得别的组件可通过 jsx 嵌套,将任意组件作为子组件传递给它们
function WelcomeDialog() {
  return (
    <FancyBorder color="blue">
      <h1>Welcome</h1>
      <p>Thank you for visiting our spacecraft!</p>
    </FancyBorder>
  )
}

2. 少数情况:将所需要内容传入 props,并使用相应的 prop

function SplitPane(props) {
  return (
    <div>
      <div>{ props.left }</div>
      <div>{ props.right }</div>
    </div>
  )
}
function App() {
  return (
    <SplitPane
     left={ <Contacts /> }
     right={ <Chat /> }
    >
  )
}

8.2 特例关系

1. 特殊组件可以通过 props 定制并渲染一般组件

function Dialog() {
  return (
    <FancyBorder color="blue">
      <h1>{ props.title }</h1>
      <p>{ props.message }</p>
    </FancyBorder>
  )
}
function WelcomeDialog() {
  return (
    <Dialog
     title="Welcome"
     message="Thank you for visiting our spacecraft!"
    />
  )
}

2. 组合 + 特殊组件

function Dialog() {
  return (
    <FancyBorder color="blue">
      <h1>{ props.title }</h1>
      <p>{ props.message }</p>
      { props.children }
    </FancyBorder>
  )
}
class SignUpDialog extends React.Component {
  constructor(props) {
    super(props)
    this.handleChange = this.handleChange.bind(this)
    this.handleSignUp = this.handleSignUp.bind(this)
    this.state = { login: '' }
  }
  render() {
    return (
      <Dialog title="Welcome"
              message="How should we refer to you?"
      >
        <input value={ this.state.login }
               onChange={ this.handleChange } />
        <button onClick={ this.handleSignUp}>
          Sign Me Up!
        </button>
      </Dialog>
    )
  }
  handleChange(e) {
    this.setState({ login: e.target.value })
  }
  handleSignUp(e) {
    talert(`Welcome abord, ${this.state.login}!`)
  }
}

8.3 继承

  • 如果你想要在组件间复用非 UI 的功能,我们建议将其提取为一个单独的 js 模块,如函数、对象或者类

  • 组件可以直接引入(import)而无需通过 extend 继承它们

posted on 2021-09-24 11:14  pleaseAnswer  阅读(42)  评论(0编辑  收藏  举报