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 <div>Hello World</div>
}
export default App
类组件
import React, { Component } from 'react'
class App extends Component {
  render() {
    return <div>Hello World</div>
  }
}
export default App

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

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

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

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

Fragment

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

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

class TodoList extends Component {
  render() {
    return (
      <Fragment>
        <div>
          <input type="text" />
          <button>提交</button>
        </div>
        <ul>
          <li>学英语</li>
          <li>学React</li>
        </ul>
      </Fragment>
    )
  }
}
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 (
      <Fragment>
        <div>
          <input value={this.state.inputValue} type="text" />
          <button>提交</button>
        </div>
        <ul>
          <li>学英语</li>
          <li>学react</li>
        </ul>
      </Fragment>
    )
  }
}
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 (
      <Fragment>
        <div>
          <input value={this.state.inputValue} onChange={this.handleInputChange.bind(this)} type="text" />
          <button>提交</button>
        </div>
        <ul>
          <li>学英语</li>
          <li>学react</li>
        </ul>
      </Fragment>
    )
  }
  handleInputChange(e) {
    this.setState({
      inputValue: e.target.value
    })
  }
}
export default TodoList

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

循环
<ul>
  {this.state.list.map((item, index) => {
    return <li key={index}>{item}</li>
  })}
</ul>

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属性

<input className="myInput" type="text" />
插入HTML

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

<ul>
  {this.state.list.map((item, index) => {
    return <li key={index} onClick={this.handleItemDelete.bind(this, index)} dangerouslySetInnerHTML={{ __html: item }}></li>
  })}
</ul>
htmlFor

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

<label htmlFor="name"></label>
<input id="name" className="myInput" value={this.state.inputValue} onChange={this.handleInputChange.bind(this)} type="text" />
组件传值
父组件向子组件传递数据

parent

<ul>
  {this.state.list.map((item, index) => {
    return (
      <div key={index}>
        <TodoItem content={item} />
      </div>
    )
  })}
</ul>

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

child

import React, { Component } from 'react'

class TodoItem extends Component {
  render() {
    return <div>{this.props.content}</div>
  }
}

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 (
      <Fragment>
        <div>
          <label htmlFor="name">姓名</label>
          <input id="name" className="myInput" value={this.state.inputValue} onChange={this.handleInputChange.bind(this)} type="text" />
          <button onClick={this.handleBtnClick.bind(this)}>提交</button>
        </div>
        <ul>
          {this.state.list.map((item, index) => {
            return (
              <div key={index}>
                <TodoItem content={item} index={index} deleteItem={this.handleItemDelete.bind(this)} />
              </div>
            )
          })}
        </ul>
      </Fragment>
    )
  }
  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 <div onClick={this.handleClick}>{this.props.content}</div>
  }
  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 (
      <Fragment>
        <div>
          <label htmlFor="name">姓名</label>
          <input id="name" className="myInput" value={this.state.inputValue} onChange={this.handleInputChange.bind(this)} type="text" />
          <button onClick={this.handleBtnClick}>提交</button>
        </div>
        <ul>{this.getTodoItem()}</ul>
      </Fragment>
    )
  }
  getTodoItem() {
    return this.state.list.map((item, index) => {
      return <TodoItem content={item} index={index} deleteItem={this.handleItemDelete} />
    })
  }
  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 <div>{this.props.content}</div>
  return (
    <div>
      <span>JSX</span>
    </div>
  )
}

原生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结点的方法

<input
  ref={(input) => (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

配置map

发送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添加类名

<input
  ref={(input) => (this.input = input)}
  id="name"
  className={this.state.show ? 'show' : 'hide'}
  value={this.state.inputValue}
  onChange={this.handleInputChange.bind(this)}
  type="text"
/>
<button onClick={this.handleBtnClick}>提交</button>

点击按钮时让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使用
<Fragment>
  <CSSTransition
    unmountOnExit
    onEntered={(el) => {
      el.style.color = 'red'
    }}
    classNames="fade"
    in={this.state.show}
    timeout={1000}
  >
    <div>React Animation</div>
  </CSSTransition>
  <button onClick={this.handleBtnClick}>开始动画</button>
</Fragment>
  • 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 (
    <Fragment>
      <TransitionGroup>
        {this.state.list.map((item, index) => {
          return (
            <CSSTransition
              unmountOnExit
              onEntered={(el) => {
                el.style.color = 'blue'
              }}
              classNames="fade"
              in={this.state.show}
              timeout={1000}
              key={index}
            >
              <div>{item}</div>
            </CSSTransition>
          )
        })}
      </TransitionGroup>
      <button onClick={this.handleBtnClick}>开始动画</button>
    </Fragment>
  )
}

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;
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,839评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,543评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,116评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,371评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,384评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,111评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,416评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,053评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,558评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,007评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,117评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,756评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,324评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,315评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,539评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,578评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,877评论 2 345