React学习笔记

React是一个由Facebook开发和主要维护的,JavaScript库,它不是框架,因为它只关注视图层

创建项目

这里介绍两种方式

第一种,安装官方提供的脚手架工具

npm install -g create-react-app
create-react-app todolist

第二种,使用npm自带的npx

npx create-react-app todolist
项目目录
|____public # 公共资源文件
| |____favicon.ico
| |____index.html
| |____logo512.png
| |____manifest.json # 桌面图标
| |____robots.txt
| |____logo192.png
|____package.json
|____src #项目源码
| |____reportWebVitals.js
| |____App.css
| |____index.js # 项目入口
| |____index.css
| |____App.test.js
| |____setupTests.js
| |____logo.svg
| |____App.js
组件
函数组件
function App() {
  return 
Hello World
} export default App
类组件
import React, { Component } from 'react'
class App extends Component {
  render() {
    return 
Hello World
} } export default App

在函数中写HTML标签时JSX语法,为了能够正常编译,必须引入React

使用自定义组件
import React from 'react'
import ReactDOM from 'react-dom'
import App from './App'

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

在使用自定义组件时首字母必须大写,正确写法,错误写法

Fragment

JSX语法规定组件最外层必须有一个元素包裹,如果不希望这个元素被渲染出来,可以使用占位符

import React, { Component, Fragment } from 'react'

class TodoList extends Component {
  render() {
    return (
      
        
  • 学英语
  • 学React
) } } export default TodoList
React响应式和事件绑定
数据绑定
import React, { Component, Fragment } from 'react'

class TodoList extends Component {
  constructor(props) {
    super(props)
    // state 存储组件的状态
    this.state = {
      inputValue: 'hello',
      list: []
    }
  }
  render() {
    return (
      
        
  • 学英语
  • 学react
) } } export default TodoList

React通过state中存储组件的状态。将属性的值绑定到变量的语法为属性名={this.state.变量名}

事件绑定
import React, { Component, Fragment } from 'react'

class TodoList extends Component {
  constructor(props) {
    super(props)
    // state 存储组件的状态
    this.state = {
      inputValue: 'hello',
      list: []
    }
  }
  render() {
    return (
      
        
  • 学英语
  • 学react
) } handleInputChange(e) { this.setState({ inputValue: e.target.value }) } } export default TodoList

React中绑定事件的语法为事件名={this.事件处理函数.bind(this)},React改变state中的值必须通过this.setState({ key: newVal })

循环
    {this.state.list.map((item, index) => { return
  • {item}
  • })}

React循环需要为循环出来的每一项增加一个key值,建议不要用index作为key

对数组的修改

React中直接修改数组可以生效,但不建议这样做,因为这会对React性能造成影响,应该先拷贝一份出来再通过setState进行修改

handleItemDelete(index) {
  const list = [...this.state.list]
  list.splice(index, 1)
  this.setState({
    list
  })
}
注释

单行注释

{
  // 单行注释
}

多行注释

{/* 多行注释 */}
className

React中用className代替了HTML中的class属性


插入HTML

React进行数据渲染时会对数据进行转义,如果需要展示HTML文本,可以使用dangerouslySetInnerHTML,但值得注意的是,这种方式存在xss攻击的可能

    {this.state.list.map((item, index) => { return
  • })}
htmlFor

为了不和JSX语法中的for循环冲突,label中的for用htmlFor进行替换



组件传值
父组件向子组件传递数据

parent

    {this.state.list.map((item, index) => { return (
    ) })}

父组件向子组件传值语法属性名={变量}

child

import React, { Component } from 'react'

class TodoItem extends Component {
  render() {
    return 
{this.props.content}
} } export default TodoItem

子组件获取父组件传递的值语法this.props.属性名

子组件向父组件传递数据

React可以通过调用父组件传递过来的方法来修改父组件中的参数

parent

import React, { Component, Fragment } from 'react'
import TodoItem from './TodoItem'
class TodoList extends Component {
  constructor(props) {
    super(props)
    // state 存储组件的状态
    this.state = {
      inputValue: '',
      list: []
    }
  }
  render() {
    return (
      
        
    {this.state.list.map((item, index) => { return (
    ) })}
) } handleInputChange(e) { this.setState({ inputValue: e.target.value }) } // 删除TODO handleItemDelete(index) { const list = [...this.state.list] list.splice(index, 1) this.setState({ list }) } // 新增TODO handleBtnClick(e) { this.setState({ list: [...this.state.list, this.state.inputValue], inputValue: '' }) } } export default TodoList

传递的方法的同时需要通过bind修改this指向,否则在子组件使用时会出现xxx not a function的错误

child

import React, { Component } from 'react'

class TodoItem extends Component {
  constructor(props) {
    super(props)
    // 将 this 绑定放到构造函数中执行可以提升代码效率
    this.handleClick = this.handleClick.bind(this)
  }
  render() {
    return 
{this.props.content}
} handleClick() { // 调用父组件传递的方法修改父组件参数 this.props.deleteItem(this.props.index) } } export default TodoItem
React代码优化
  1. 将函数this绑定放到构造函数中
import React, { Component } from 'react'

class TodoItem extends Component {
  constructor(props) {
    super(props)
    // 将 this 绑定放到构造函数中执行可以提升代码效率
    this.handleClick = this.handleClick.bind(this)
  }
}

export default TodoItem
  1. 使用es6的解构赋值简化代码
import React, { Component } from 'react'

class TodoItem extends Component {
  handleClick() {
    const { deleteItem, index } = this.props
    // 调用父组件传递的方法修改父组件参数
    deleteItem(index)
  }
}

export default TodoItem
  1. setState传递一个方法
import React, { Component, Fragment } from 'react'
import TodoItem from './TodoItem'
class TodoList extends Component {
  handleInputChange(e) {
    this.setState(() => {
      return {
        inputValue: e.target.value
      }
    })
  }
}
export default TodoList
  1. 使用prevState
import React, { Component, Fragment } from 'react'
import TodoItem from './TodoItem'
class TodoList extends Component {
  constructor(props) {
    super(props)
    // state 存储组件的状态
    this.state = {
      inputValue: '',
      list: []
    }
    this.handleBtnClick = this.handleBtnClick.bind(this)
    this.handleItemDelete = this.handleItemDelete.bind(this)
    this.getTodoItem = this.getTodoItem.bind(this)
  }
  render() {
    return (
      
        
    {this.getTodoItem()}
) } getTodoItem() { return this.state.list.map((item, index) => { return }) } handleInputChange(e) { this.setState(() => { return { inputValue: e.target.value } }) } // 删除TODO handleItemDelete(index) { this.setState((prevState) => { const list = [...prevState.list] list.splice(index, 1) return { list } }) } // 新增TODO handleBtnClick(e) { // prevState 是修改前的 state,这是一种更严谨的写法 this.setState((prevState) => { return { list: [...prevState.list, prevState.inputValue], inputValue: '' } }) } } export default TodoList
  1. shouldComponentUpdate

child component

shouldComponentUpdate(nextProps, nextState) {
  if (nextProps.content !== this.props.content) {
    return true
  } else {
    return false
  }
}

一般情况下,父组件调用render后子组件也会调用render,但有时子组件并不需要更新,这是可以通过shouldComponentUpdate返回false来避免重新渲染子组件

React特性
  • 声明式开发
  • 可以与其他框架并存
  • 组件化
  • 单向数据流

防止子组件意外修改父组件的值造成调试困难

  • 视图层框架

  • 函数式编程

React开发工具的安装和使用

Chrome插件 React Developer Tools

可以对数据进行监听

参数校验和参数默认值

propTypes设置参数校验规则,defaultProps设置参数默认值

TodoItem.propTypes = {
  test: PropTypes.string.isRequired,
  content: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
  deleteItem: PropTypes.func,
  index: PropTypes.number
}
TodoItem.defaultProps = {
  test: '请输入todo内容'
}
props、state与render的关系

当组state或者props发生改变时,render`函数就会重新执行

当父组件的render重新执行时,子组件的render也会重新执行

虚拟DOM

state 数据

JSX 模板

数据+模板结合,生成真实DOM并进行显示

生成虚拟DOM(虚拟DOM就是一个JS对象,用它来描述真实DOM)

state 发生变化时生成新的虚拟DOM(极大提升了性能)

比较原始虚拟DOM和新的虚拟DOM的区别(极大提升了性能)

直接操作DOM,改变DOM中内容

深入理解虚拟DOM

以下两种语法是等价的

JSX语法

render() {
  // return 
{this.props.content}
return (
JSX
) }

原生JS语法

render() {
  return React.createElement('div', {}, React.createElement('span', {}, 'JS'))
}
Diff算法

React的虚拟DOM进行同层比对,当某一结点不同时,其子孙结点都会被替换。优点是算法简单,缺点是可能造成性能问题。

key可以大大提升Diff算法的效率:key可以建立新虚拟DOM的结点和原虚拟DOM结点的对应关系,方便进行比对。如果使用index作为key,由于index是不稳定的,这个时候key就不生效了。

ref

React提供的获取DOM结点的方法

 (this.input = input)}
/>

这条语句的作用是将input挂载到this.input

使用ref获取DOM元素

handleInputChange() {
  this.setState(() => {
    return {
      inputValue: this.input.value
    }
  })
}

使用ref需要注意的问题

handleBtnClick(e) {
  // prevState 是修改前的 state,这是一种更严谨的写法
  this.setState((prevState) => {
    return {
      list: [...prevState.list, prevState.inputValue],
      inputValue: ''
    }
  })
  console.log(this.ul.querySelectorAll('li').length)
}

由于修改state是异步的,打印语句会先执行,所以length总比真实情况少1

解决方案

将代码放到setState的回调函数中,可以确保获取的修改后的state

handleBtnClick(e) {
  // prevState 是修改前的 state,这是一种更严谨的写法
  this.setState(
    (prevState) => {
      return {
        list: [...prevState.list, prevState.inputValue],
        inputValue: ''
      }
    },
    () => {
      console.log(this.ul.querySelectorAll('li').length)
    }
  )
}
React生命周期函数

生命周期函数指在某一个时刻组件会自动调用执行的函数

钩子函数 触发行为 在此阶段可以做的事情
constructor 数据初始化时
componentWillMount 在组件即将被挂载到页面时刻执行
render 在组件渲染时执行
componentDidMount 在组件被挂载到页面后执行 发送ajax请求
componentWillReceiveProps 从父组件接收改变后的props时执行(父组件重新执行render之后)
shouldComponentUpdate 组件更新前触发,返回true更新,返回false不更新,之后的钩子函数都不执行
componentWillUpdate 组件更新前,shouldComponentUpdate执行后触发,必须上一阶段返回true才会执行
componentDidUpdate 组件更新后执行
componentWillUnmount 组件即将从页面移除时执行

子组件移除过程 shouldComponentUpdate->componentWillUpdate->componentWillUnmount(子组件)->componentDidUpdate

Charles实现本地数据mock

由于Charles不再支持捕获localhost的请求,所以必须让项目支持用域名访问,在React项目中可以进行以下修改

package.json

"scripts": {
  "start": "set PORT=3000 HOST=localhost.charlesproxy.com && react-scripts start"
},

Charles配置

打开Charles,Tools->Map Local->Enable Map Local

开启local map

DplseK.png

配置map

DpluGj.png

发送ajax请求

async componentDidMount() {
  const res = await axios.get('/api/todolist')
  this.setState(() => {
    return {
      list: [...res.data]
    }
  })
}
React动画

css

.show {
  opacity: 1;
  transition: all 1s ease-in;
}
.hide {
  opacity: 0;
  transition: all 1s ease-in;
}

jsx

根据show添加类名

 (this.input = input)}
  id="name"
  className={this.state.show ? 'show' : 'hide'}
  value={this.state.inputValue}
  onChange={this.handleInputChange.bind(this)}
  type="text"
/>

点击按钮时让showtruefalse之间切换

handleBtnClick(e) {
  // prevState 是修改前的 state,这是一种更严谨的写法
  this.setState(
    (prevState) => {
      return {
        list: [...prevState.list, prevState.inputValue],
        inputValue: '',
        show: !prevState.show
      }
    },
    () => {
      console.log(this.ul.querySelectorAll('li').length)
    }
  )
}
react-transition-group实现动画

文档地址https://reactcommunity.org/react-transition-group/

安装依赖

yarn add react-transition-group

导入依赖

import { CSSTransition } from 'react-transition-group'
CSSTransition使用

   {
      el.style.color = 'red'
    }}
    classNames="fade"
    in={this.state.show}
    timeout={1000}
  >
    
React Animation
  • in={this.state.show} 根据show的值判断当前动画的状态,是处于出场动画还是入场动画

  • timeout={1000} 设置动画执行时间,单位毫秒

  • classNames="fade" 动画名称

  • unmountOnExit 在退出动画时移除元素

  • appear={true} 在元素第一次出现时显示动画

  • 动画过渡期间的钩子函数onEnter、onEntering 、onEntered(入场动画结束后)、onExit、onExiting、onExited

动画过渡期间的类名

  • fade-enter 入场动画前
  • fade-enter-active 入场动画中
  • fade-enter-done 入场动画后
  • fade-exit 出场动画前
  • fade-exit-active 出场动画中
  • fade-exit-done 出场动画后
TransitionGroup使用

TransitionGroup是一个组件,用于列表项动画。以下是列表新增元素动画的例子

js

render() {
  return (
    
      
        {this.state.list.map((item, index) => {
          return (
             {
                el.style.color = 'blue'
              }}
              classNames="fade"
              in={this.state.show}
              timeout={1000}
              key={index}
            >
              
{item}
) })}
) }

css

.fade-enter {
  opacity: 0;
}

.fade-enter-active {
  opacity: 1;
  transition: opacity 1s ease-in;
}

.fade-enter-done {
  opacity: 1;
}

.fade-exit {
  opacity: 1;
}

.fade-exit-active {
  opacity: 0;
  transition: opacity 1s ease-in;
}

.fade-exit-done {
  opacity: 0;
}

你可能感兴趣的:(React学习笔记)