react笔记

React介绍

起源

React 起源于 Facebook 的内部项目,因为该公司对市场上所有 JavaScript MVC 框架,都不满意,就决定自己写一套,用来架设Instagram 的网站。做出来以后,发现这套东西很好用,就在2013年5月开源了。

React与传统MVC的关系

轻量级的视图层库。React不是一个完整的mvc框架,最多可以认为是MVC中的V(view),甚至React并不非常认可MVC开发模式;React构建页面UI库,可以理解为,每一块就是组件,这些组件之间可以组合,嵌套,就成了我们的页面。

React的特性

  1. 声明式设计-React采用声明式,可以轻松描述应用。
  2. 高效-React通过对Dom的模拟(虚拟Dom),最大限度地减少与Dom的交互
  3. 灵活-React可以与已知的库或框架很好地配合
  4. JSX-jsx是javascript语法的扩展
  5. 组件-通过React构建组件,使得代码更加容易得到复用,能够很好的应用在大项目的开发中
  6. 单向响应的数据流-React实现了单向响应的数据流,从而减少了重复代码,这也是为什么比传统数据绑定更简单。

create-react-app

// 全局安装create-react-app
npm install -g create-react-app
// 创建一个项目
create-react-app your-app

// 局部安装
npx create-react-app your-app

切换镜像源

npm i -g nrm
// 列表
nrm ls
nrm use taobao

常见问题:
npm 安装失败

  1. 切换为npm的淘宝镜像
  2. 使用yarn,如果本来使用yarn还有失败,还得把yarn的源切换到国内
  3. 如果还没有办法解决,删除node_modules及package-lock.json然后重新执行npm install命令
  4. 再不能解决就删除node_modules及package-lock.json的同时清除npm缓存npm cache clean --force之后再执行npm install 命令

jsx语法

jsx将html语法直接加入到javascript代码中,再通过翻译器转换到纯javascript后由浏览器执行。在实际开发中,jsx在产品打包阶段都已经编译成纯javascript,不会带来任何副作用,反而会让代码更加直观并易于维护。编译过程由babel的jsx编译器实现。

所谓的jsx其实就是javascript对象,所以使用React和jsx的时候一定要经过编译的过程:

  • jsx 使用react构造组件,babel进行编译->javascript对象->ReactDOM.render()->DOM元素->插入页面

class组件

Es6的加入让javascript直接支持使用class来定义一个类,react创建组件的方式就是使用的类的继承,Es6 class是目前官方推荐的使用方式,它使用了ES6标准语法来构建

import React from 'react'
import ReactDOM from 'react-dom'

class App extend React.Component{
  render() {
    return (
      <h1>欢迎进入react的世界</h1>
    )
  }
}

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

函数式组件

import React from 'react'
import ReactDOM from 'react-dom'

const App = (props) => <h1>hello world</h1>

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

注意:组件名必须大写,否则报错

组件的样式

  • 行内样式
    想给虚拟dom添加行内样式,需要使用表达式传入样式对象的方式来实现:
// 注意这里的两个括号,第一个表示我们在要jsx里插入js了,第二个是对象的括号
<p style="{{color: 'red',fontSize: '14px'}}">hello world</p>

行内样式需要写入一个样式对象,而这个样式对象的位置可以放在很多地方,列如render函数里,组件原型上、外链js文件中

  • 使用class
    React推荐我们使用行内样式,因为React觉得每一个组件都是一个独立的整体
    其实我们大多数情况下还是大量的在为元素添加类名,但是需要注意的是,class需要写成className(因为毕竟是在写类js代码,会收到js规则的)
<p className="hello">hello word</p>

事件处理

绑定事件

采用on+事件名的方式来绑定一个事件,注意,这里和原生的事件是有区别的,原生的事件全是小写onclick,React里的事件是驼峰onClick,React的事件并不是原生事件,而是合成事件。
React并不会真正的绑定事件到每一个具体的元素上,而是采用事件代理的模式;

事件handler的写法

  • 直接在render里写行内的箭头函数(不推荐)
  • 在组件内使用箭头函数定义一个方法(推荐)
  • 直接在组件内定义一个非箭头函数的方法,然后再render里面直接使用onClick={this.handleClick.bind(this)}(不推荐)
  • 直接在组件内定义一个非箭头函数的方法,然后在constructor里bind(this)(推荐)

Event对象

和普通浏览器一样,事件handler会被自动传入一个event对象,这个对象和普通的浏览器event对象所包含的方法和属性都基本一致。不同的是React中的event对象并不是浏览器提供的,而是它自己内部所构建的。它同样具有event.stopPropagation、event.preventDefault这种常用的方法

Ref的应用

  1. 给标签设置ref=“username”
    通过这个获取this.$refs.username, ref可以获取到应用的真实dom
  2. 给组件设置ref=“username”
    通过这个获取this.$refs.username, ref可以获取到组件对象
  3. 新的写法
myRef = React.createRef()
<div ref={this.myRef}>hello</div>
访问this.myRef.current

组件的数据挂载方式

1. 状态(state)

状态就是组件描述某种显示情况的数据,由组件自己设置和更改,也就是说由组件自己维护,使用状态的目的就是为了在不同的状态下使组件的显示不同(自己管理)

  1. 定义state
    第一种方式
import React, { Component } from 'react'
import ReactDOM from 'react-dom'

class App extends Component {
  state = {
    name: 'React',
    isLiked: false
  }
  render(){
    return (
      <div>
         <h1>欢迎来到{this.state.name}的世界</h1>
         <button onClick={this.handleClick}>{ this.state.isLiked ? '取消' : '收藏'}</button>
      </div>
   )
  }
  handleClick = () => {
    this.setState({
      isLiked: !this.state.isLiked
    })
  }
}

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

this.state是纯js对象,在vue中,data属性是利用object.defineProperty处理过的,更改data的数据的时候会触发数据的getter和setter,但是React中没有这么处理,如果直接更改的话,react是无法得知的,所以,需要使用特殊的更改状态的方法setState.

  1. setState
    isLiked存放在实体的state对象中,组件的render函数内,会根据组件的state的中的isLiked不同显示"取消"或"收藏"内容。

setState处在同步的逻辑中,异步更新状态,更新真实DOM
setState处在异步的逻辑中,同步更新状态,同步更新真实Dom
setState接受第二个参数,第二个参数式回调函数,状态和dom更新完后就会被触发

循环渲染

import React, { Component } from 'react'
export default class App extends Component {
  state = {
    list: [
      {id: 1, text: 1},
      {id: 2, text: 2},
      {id: 3, text: 3},
    ]
  }
  render() {
    return (
      <div>
        <ul>
          {
            this.state.list.map(item => <li key={item.id}>{item.text}</li>)
          }
        </ul>
      </div>
    )
  }
}

React的高效依赖于所谓的Virtcual-DOM,尽量不碰DOM。对于列表元素来说会有一个问题:元素可能会在一个列表中改变位置。要实现这个操作,只需要交换一下DOM位置就行,但是React并不知道其实我们只是改变了元素的位置,所以它会重新渲染后面两个元素(再执行Virtual-DOM),这样会大大增加DOM操作。但如果给每个元素加上唯一的标识,React就可以知道这两个元素只是交换了位置,这个标识就是key,这个key必须是每个元素唯一的标识。

2.属性(props)

props是正常是外部传入的,组件内部也可以通过一些方式来初始化的设置,属性不能被组件自己更改,但是你可以通过父组件主动重新渲染的方式来传入新的props
属性是描述性质、特点,组件自己不能随意更改。
之前的组件代码里面有props的简单使用,总的来说,在使用一个组件的时候,可以把参数放在标签的属性当中,所有的属性都会作为组件props对象的键值。通过箭头函数创建的组件,需要通过函数的参数来接收props:

  • 在组件上通过key=value写属性,通过this.props获取属性,这样组件的可复用性提高了。
  • 注意在传参数时候,如果写成isShow="true"那么这是一个字符串 如果写成isShow={true}这个是布尔值
  • {…对象}展开赋值
  • 默认属性值
*。defaultProps = {}
static defaultProps = {
  myname: "默认的myname",
  myshow: true
}

3. 属性vs状态

相似点:都是纯js对象,都会触发render更新,都具有确定性(状态/属性相同,结果相同)
不同点:

  • 属性能从父组件获取,状态不能
  • 属性可以由父组件修改,状态不能
  • 属性能在内部设置默认值,状态也可以,设置方式不一样
  • 属性不在组件内部修改,状态要在组件内修改
  • 属性能设置子组件初始值,状态不可以
  • 属性可以修改子组件的值,状态不可以

state 的主要作用是用于组件保存、控制、修改自己的可变状态。state在组件内部初始化,可以被组件自身修改,而外部不能访问也不能修改。你可以认为state是一个局部的,只能被组件自身控制的数据源。state中状态可以通过this.setState方法进行更新,setState会导致组件的重新渲染。
props的主要作用是让使用该组件的父组件可以传入参数来配置该组件。它是外部传进来的配置参数,组件内部无法控制也无法修改。除非外部组件主动传入新的props,否则组件的props永远保持不变。
没有state的组件叫无状态组件,设置了state的叫做由状态组件。因为状态会代理管理的复杂性,我们尽量多地写无状态组件,尽量少地写由状态组件。这样会降低代码维护的难度,也会在一定程度上增强组件的可复用性。

渲染数据

条件渲染

{
  condition ? '渲染列表的代码':'空空如也'
}

表单中的受控组件与非受控组件

  1. 非受控组件
    React要编写一个非受控组件,可以使用ref来从DOM节点中获取表单数据,就是非受控组件。
    因为非受控组件将真实数据存储在DOM节点中,所以在使用非受控组件时,有时候反而更容易同时集成React和非React代码。如果你不介意代码美观性,并且希望快速编写代码,使用非受控组件往往可以减少您的代码量。否则,你应该使用受控组件。

默认值
在React渲染生命周期时,表单元素上的value将会覆盖DOM节点的值,在非受控组件中,你经常希望React能赋予组件一个初始值,但是不去控制后续的更新。在这种情况下,你可以指定一个defaultValue属性,而不是value.

render() {
  return (
    <form onSubmit={this.handleSubmit}>
       <label> name: <input defaultValue = "Bob" type="text" ref={this.input} />
       <input type="submit" value="submit" />
    </form>
  )
}

因为非受控组件将真实数据储存在DOM节点中,所以在使用非受控组件时,有时候反而更容易同时集成React和非React代码。如果你不介意代码美观性,并且希望快速编写代码,使用非受控组件往往可以减少你的代码量,否则,你应该使用受控组件

  1. 受控组件
    在HTML中,表单元素(如、和)通常自己维护state,并根据用户输入进行更新。而在React中,可变状态(mutable state)通常保存在组件的state属性中,并且只能通过使用setState()来更新。
    我们可以把两者结合起来,使React的state成为唯一数据源。渲染表单的React组件还控制着用户输入过程中表单发生的操作。被React以这种方式控制取值的表单输入元素就叫做“受控组件”。
class NameForm extends React.Component {
   constructor(props) {
      super()
      this.state = { value: "" }
      
      this.handleChange = this.handleChange.bind(this)
      this.handleSubmit = this.handleSubmit.bind(this)
  }
  handleChange(event) {
    this.setState({value: event.target.value})
  }
  handleSubmit(event) {
    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>
    )
  }
}

由于在表单元素上设置了value属性,因此显示的值将始终为this.state.value,这使得React的state成为唯一数据源。由于handleChange在每次按键时都会执行并更新React的state,因此显示的值将随着用户输入而更新。
对于受控组件来说,输入的值始终由React的state驱动。你也可以将value传递给其他UI元素,或者通过其他事件处理函数重置,但这意味者你需要编写更多的代码。

注意: 其它说法(广义范围的说法),React组件的数据渲染是否被调用者传递的props完全控制,控制则为受控组件,否则非受控组件

组件通信的方式

父子组件通信方式
  • 传递数据(父传子)与传递方法(子传父)
  • ref标记(父组件拿到子组件的引用,从而调用子组件的方法)
    在父组件中清除子组件的input输入框的value值。this.refs.form.reset()

非父子组件通信方式

  1. 状态提升(中间人模式)
    React中的状态提升概括来说,就是将多个组件需要共享的状态提升到它们最近的父组件上,在父组件上改变这个状态然后通过props分发给子组件。
  2. 发布订阅模式
var bus = {
  list: [],
  // 订阅
  subscribe(callback) {
    this.list.push(callback)
  },
  // 发布
  publish(text) {
    this.list.forEach(callback => {
      callback && callback(text)
    })
  }
}
  1. context 状态树传参
a. 先定义全局context对象
import React from 'react'
const GlobalContext = React.createContext()
export default GlobalContext

b. 根组件引入GlobalContext,并使用GlobalContext.Provider(生产者)
// 重新包装根组件 class App {}
<GlobalContext.Provider 
  value = {{ name: "kerwin", age: 100, content: this.state.content, show: this.show.bind(this), hide: this.hide.bind(this)}}>
 <之前的根组件 />
</GlobalContext.Provider>

c. 引用组件引入GlobalContext,并使用GlobalContext.Consumer(消费者)
<GlobalContext.Consumer>
  {  (value) => {
     return (
       <之前的根组件 />
     )
   } }
</GlobalContext.Consumer>

React 生命周期

1. 初始化阶段

  1. componentWillMount: render之前最后一次修改状态的机会
  2. render: 只能访问this.props和this.state,不允许修改状态和DOM输出
  3. componentDidMount: 成功render并渲染完成真实DOM之后触发,可以修改DOM

2.运行中阶段

  1. componentWillReceiveProps: 父组件修改属性触发
  2. shouldComponentUpdate: 返回false会阻止render调用
  3. componentWillUpdate: 不能修改属性和状态
  4. render: 只能访问this.props和this.state,不允许修改状态和DOM输出
  5. componentDidUpdate: 可以修改DOM

3.销毁阶段

  1. componentWillUnmount: 在删除组件之前进行清理操作,比如计时器和事件监听器

老生命周期的问题

  1. componentWillMount, 在ssr中这个方法将会被多次调用,所以会重复出发多遍,同时在这里如果绑定事件,将无法解绑,导致内存泄漏,变得不够安全高效逐步废弃。
  2. componentWillReceiverProps 外部组件多次频繁更新传入多次不同的props,会导致不必要的异步请求
  3. componentWillupdate,更新前记录DOM状态,可能会做一些处理,与componentDidUpdate相隔时间如果过长,会导致 状态不太信

新生命周期函数替代

  1. getDerivedStateFromProps 第一次的初始化组件以及后续的更新过程中(包括自身状态更新以及父传子),返回一个对象作为新的state,返回null则说明不需要在这里更新state
  2. getSnapshotBeforeUpdate 取代了componentWillUpdate,触发时间为update发生的时候,在render之后dom渲染之前返回一个值,作为componentDidUpdate的第三个参数。

react中性能优化的方案

  1. shouldComponentUpdate
    控制组件自身或者子组件是否需要更新,尤其在子组件非常多的情况下,需要进行优化。
  2. PureComponent
    PureComponent会帮你 比较新props跟旧的props,新的state和老的state(值相等,或者对象含有相同的属性,且属性值相等),决定shouldcomponentUpdate 返回true或者false,从而决定要不要呼叫render function.

注意:
如果你的state或props 【永远都会变】,那PureComponent并不会比较快,因为shallowEqual也需要花时间。

React Hooks

使用hooks理由

  1. 高阶组件为了复用,导致代码层级复杂
  2. 生命周期的复杂
  3. 写成function组件,因为需要状态,又改成了class,成本高

userState(保存组件状态)

const [state, setState] = userState(initialState)

useEffect(处理副作用)和useLayoutEffect(同步执行副作用)

** Function Component不存在生命周期,所以不要把class Component的生命周期概念搬过来试图对号入座

useEffect(() => {
   // effect
   return () => {
     // cleanup
   }
}, [依赖的状态; 空数组,表示不依赖])

不要对Dependencies 撒谎,如果你明明使用了某个变量,却没有声明在依赖中,你等于向React 撒了谎,后果就是,当依赖的变量改变时,useEffect也不会再次执行,eslint会报警告
Preview页面改造成函数组件,在路径上从id=1切换到id=2也会自动重新加载,比class组件方便

let id = props.match.params.myid
useEffect(() => {
  axios.get(`/articles/${id}`).then(res => {
    settitle(res.data.title)
    setcontent(res.data.content)
    setcategory(res.data.category)
  })
},[id])

useEffect和useLayoutEffect有什么区别?
简单来说就是调用时机不同,useLayoutEffect和原来componentDidMount & componentDidupdate一致,在react完成DOM更新后马上同步调用的代码,会阻塞页面渲染。而useEffect是会在整个页面渲染完才会调用的代码。
官方建议先使用 useEffect
在实际使用时如果想避免页面抖动(在useEffect里修改DOM很有可能出现)的话,可以把需要操作DOM的代码放在useLayoutEffect里。在这里做点dom操作,这些dom修改会和react做出的更改一起被一次性渲染到屏幕上,只有一次回流,重绘的代价。

useCallback(记忆函数)

阻止因为组件重新渲染,导致方法被重新创建,起到缓存作用,只有第二个参数变化了,才重新声明一次

var handleClick = useCallback(() => {
  console.log(name)
},[name])
<button onClick={()=>handleClick()}>hello</button>
// 只有name改变后,这个函数才会重新声明一次
// 如果传入空数组,那么就是第一次创建后就被缓存,如果name后期改变了,拿到的还是老的name
// 如果不传第二个参数,每次都会重新声明一次,拿到的就是最新的name

useMeno 记忆组件

useCallback的功能完成可以由useMemo所取代,如果你想通过使用useMemo返回一个记忆函数也是完全可以的。
唯一的区别是:useCallback不会执行第一个参数函数,而是将它返回给你,而useMemo会执行第一个函数并且将函数执行结果返回给你。所以在前面的例子中,可以返回handleClick来达到存储函数的目的。
所以useCallback常用记忆事件函数,生成记忆后的事件函数并传递给子组件使用。而useMemo更适合函数计算得到一个确定的值,比如记忆组件。

useRef(保存引用值)

const myswiper = useRef(null)
<Swiper ref="{myswiper}">

useReducer和useContext(减少组件层级)

import React from 'react'
var GlobalContext = React.createContext()
// 注意此时的reduecer返回值是一个对象{isShow: false, list: []}

自定义hooks

当我们想在两个函数之间共享逻辑时,我们会把它提取到第三个函数中。
必须以"use"开头吗?必须如此,这个约定非常重要,不遵循的话,由于无法判断某个函数是否包含对某内部Hook的调用,React将无法自动检查你的Hook是否违反了HooK的规则

4. 项目注意

1. 方向代理

https://facebook.github.io/create-react-app/docs/proxying-api-requests-in-development

npm install http-proxy-middleware -- save

const { createProxyMiddleware } = require('http-proxy-middleware')

module.exports=function(app){
  app.use(
    '/api',
    createProxyMiddleware({
      target:'http://localhost:5000',
      changeOrigin:true
    })
  )
}

css module

https://facebook.github.io/create-react-app/docs/adding-a-css-modules-stylesheet

全局
:global(.active){}

styled-components

它是通过javascript改变css编写方式的解决方案之一,从根本上解决常规css编写的一些弊端。
通过javascript来为css赋能,我们能达到常规css所不好处理的逻辑复杂,函数方法、复用、避免干扰。样式书写将直接依附在JSX上面,HTML、CSS、JS三者再次内聚。all in js的思想

Portal

portals提供了一个最好的在父组件包含的DOM结构层级外的Dom节点渲染组件的方法。

ReactDOM.createPortal(child,container);

第一个参数child是可渲染的react子项,比如元素,字符串或者片段等。第二个参数container是一个DOM元素。

  • 23
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值