React技术栈

简介: React技术栈

React技术栈


React

官网

中文网

用于构建用户界面(UI)的 JavaScript 库

版本:16.x / 17.x / 18.x

创建 React 项目

利用脚手架创建react项目框架 cra5.x

react直接是18.x版本,近两月出来的

之前一直用的是16.x 17.x react项目

create-react-app ( cra )

官方定义的脚手架,用于快速搭建项目结构, 创建一个新的单页应用

目前 create-react-app 升级到 5.x 版本,要求 node.js 的版本至少为 14.x 以后的版本,所以对于 win7 不支持。

创建项目

$ npx create-react-app <project-name>
# 或
$ npm install yarn -g
$ yarn create react-app <project-name>

安装依赖包

yarn 回车

react:核心包

react-dom:浏览器渲染 (react-native:原生渲染,了解)

react-scripts:封装了 webpack 各项任务的脚本

npm scripts

可以在命令行中使用 npm run 的任务:

start:开发任务

build:生产任务,打包构建生成打包后的代码

test:测试

eject:弹出(将封装好的 webpack 任务还原到项目本地,以便于在项目目录下找到 webpack 的配置文件并进行修改扩展。**注意:**执行 eject 弹出任务后,是不能撤销的)

以下是react16.x | 17.x 项目

入口文件

src/index.js 文件( 16.x 17.x):

只能复制黏贴以前的,18很多问题…

import React from 'react'
// 在 16.x 中,要使用 JSX 表达式,则必须引入 react
import ReactDOM from 'react-dom'
ReactDOM.render(
    <h1>Hello React</h1>,       document.getElementById('root')
)

18.x的写法

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<h1>Hello, world!</h1>);

JSX

JavaScript 的语法扩展。

建议在 React 中配合使用 JSX,JSX 可以很好地描述 UI 应该呈现出它应有交互的本质形式。

本质语法糖,在vue中需要借助babel插件才能使用,react天生支持

class语法糖 函数+原型链…

在JSX中嵌入 JS 表达式

const message = '<input type="text"/>'
const element = (
  <h1 title={message}>hello,{message}</h1>
  )

使用 { expression } 来嵌入 JS 表达式内容,注意:在 {} 内的 HTML 表达式内容会被转义处理,以避免出现 XSS 攻击。 XSS(cross-site-scripting, 跨站脚本)

属性值避免引号,如果有引号就是‘’字符串

需要html文本正常渲染的话:

<div>{ this.state.html }</div> // 源码
<div dangerouslySetInnerHTML={ {__html:this.state.html }}></div>  //渲染html

为了便于阅读,我们会将 JSX 拆分为多行。同时,我们建议将内容包裹在括号中,虽然这样做不是强制要求的,但是这可以避免遇到自动插入分号陷阱。

const element = (
  <h1>hello,{message}</h1>
、)

组件

1.函数组件

src/function.js 【rfc】

function MyComponent(props){ // 通过参数props接受到的属性
  return (
    <div> JSX表示的 React 元素 </div>
  )
}

return 一个JSX 表达式 (定义组件)

单个根节点包裹布局结构

哪里用到 直接import 引入 (渲染组件)

组件名字 作为 标签名字去渲染

组件名字 每个单词首字母大写 (帕斯卡命名规范)。JSX中通过标签的首字母是否大写来判定是 html标签 还是自定义的组件

2.类组件

src/class.jsx 【 rcc 】

class MyComponent extends React.Component {
  // constructor() {}
  render() {
      // this.props 获取组件接收到的属性
    return (
      <div>JSX 表示的 React 元素</div>
    )
  }
}
//自定义类,继承自 React.component 父类(基类)
//类主体中,必须写 render(){ return () }

import 引入

组件名字作为 标签名字引入

class组件必须使用render() 方法

渲染组件 { }

State 与 Props

state 是组件内私有使用到的数据,state 允许 React 组件随用户操作、网络响应或者其他变化而动态更改输出内容。

props 是组件接收到的属性数据,应该是只读的,绝不能修改组件中的 属性 数据。

正确使用 state

不能直接修改 state 的值,而是调用 setState() 方法更新状态数据。直接修改 state 数据,视图不会响应式渲染

state 的更新可能是异步的

 this.setState({
      inputValue:''
    })
console.log('input',this.state.inputValue); //undefined
setTimeout(()=>{console.log(this.state.inputValue);},1000)
// 正常显示输入框的值

属性 Props 类型检查

vue组件中props传递数据,可以定义数据类型;

react用类型检查

TypeScript || Flow :静态类型检查器

prop-types:运行时类型检查 【三方包】

$ npm i prop-types
# 或
$ yarn add prop-types
import PropTypes from 'prop-types'; // ES6
var PropTypes = require('prop-types'); // ES5 with npm
class
  /**
   * 运行时类型检查
   * 你可以在任何 PropTypes 属性后面加上 `isRequired` ,确保
   * 这个 prop 没有被提供时,会打印警告信息。
   */
  static propTypes = {
    todos:PropTypes.array.isRequired,
  }

props 默认值

  /**
   * 默认值 不需要安装包
   *  
   */
class
static defaultProps = {
    todos:[]
}

ref

vue中获取焦点 this.$refs == react中有 this.refs 【弃用】

createRef this.refs == this.inputRef.current.focus( )

创建一个ref对象 inputRef = createRef()

ref = { this.inputRef }

/* 获取焦点 */
// 拿到createRef
import React, { Component,createRef } from 'react'
// 创建一个 Ref 对象
inputRef = createRef() // React.createRef()
// 点击事件
handleButton= (event)=>{
    // 获取焦点 refs 已经弃用
    // this.refs.inputRef.focus() 
    this.inputRef.current.focus()
}
<input ref={this.inputRef}/>

组件通信

父子组件

父传子:利用props

类组件

App.jsx:
constructor(props){
    super(props) // 调用父类的构造函数
    this.state = { }
}
不显示定义 constructor 来书写 state的结构,简写以下:
/* 父传子 */
App.jsx =>  传子 todos数组
state = {
    todos:new Array(5).fill(null).map((_,index) => {
      return {
        id:Math.random(),
        completed:false,
        title:'什么是闭包'+ index,
      }
    })
 }
<TodoList todos={ this.state.todos }></TodoList>
--------------------------------------------------
Todolist.jsx: => 传子 todo 当前项
 {/* this.props 是在 class 组件的成员方法中获取属性值的调用写法 */}
render() {
    return (
      <div>
        {/* <TodoItem></TodoItem> */}
        {
          this.props.todos.map(todo => {
            return (
              <TodoItem todo={ todo }></TodoItem>
            )
          })
        }
      </div>
    )
}
------------------------------------------------------
TodoItem.jsx:
render() {
    const {id,title,completed} = this.props.todo
    return (
      <div>
            {title}--{completed?'已':'未'}完成---{id}
          </a>
      </div>
    )
}

子传父:利用props 传递函数的引用

利用 props 来传递函数的引用。父组件中渲染子组件实例时,传递一个函数的引用作为属性值到子组件中,当子组件需要传递数据时,调用在属性中接收到的函数并传递参数即可。要注意函数中 this 指向的问题(可以调用 bind 修改 this 指向,或使用箭头函数定义)

类组件

添加待办事件 方法全部用箭头函数写,不会有this困扰

// 修改state状态数据,是调用this.setState() 方法【继承自父类,react.component】来修改
/* 添加待办事件 方法全部用箭头函数写,不会有this困扰*/
App.jsx:
  addTodo = (title)=>{
    this.setState({
      todos:[
        ...this.state.todos,
        {
          id:Math.random(),
          title, // 子传父
          completed:false,
        }
      ]
    })
  }
<TodoInput add={this.addTodo}></TodoInput>
 ----------------------------------------------------
TodoInput.jsx:
/* 处理添加待办事件 
   * onKeyUp={this.handleAdd} 
   * 获取输入框的值 - 点击enter 加入todos数组
  */
  handleAdd=(event)=>{
    let title = event.target.value
    if(event.keyCode===13){
      console.log('key13');
      this.props.add(title)
      event.target.value = ''
    }
  }
  <input 
      onKeyUp={this.handleAdd}  
  />
--------------------------------------------------
/* 添加待办事件 方法全部用普通函数写,有this困扰*/
TodoInput.jsx:
//事件函数,不绑定this, this默认为undefined => 改变this指向
// bind() 改变this指向   this.bind(null) => 全局对象 严格模式下为undefined
handleAdd(event){
    event.target.value
    if(event.keyCode === 13){ // 回车
        // 调用从属性中传递的 add 函数
        this.props.add(event.target.value) 
    }
}
<input onKeyDown={ this.handleAdd.bind(this) }></input>
-------------------------------------------------------
App.jsx:
addTodo(title){
   console.log('addTodo',this); // addTodo()
    this.setState({
      todos:[
        ...this.state.todos,
        {
          id:Math.random(),
          title, // 子传父
          completed:false,
        }
      ]
    })
  }
<TodoInput add={this.addTodo.bind(this)}></TodoInput>

跨组件层级

Context

todoItem 删除 App 组件内的 todos 数组

utils> with-contenxt.js

跨组件层级
Context
todoItem 删除 App 组件内的 todos 数组
utils> with-contenxt.js

App.jsx>生产组件

import { Provider } from ''
render(){
  return (// 相当于共享数据
    <Provider value={ {
      todos:this.state.todos,
      add:this.addTodoItem,
      删除方法,
      改变状态,
    } }>
    ...把以前的用标签包起来
    </Provider>
  )
}
-----------------------------------------------------------------------------
render() {
    return (
        <Provider value={{
          todos:this.state.todos,
          add:this.addTodo,
          remove:this.removeTodo,
          toogle:this.toggleTodoItem,
        }}>
          <article className="panel is-primary" style={{width:'500px',height:'500px'}}>
            <TodoHeader></TodoHeader>
            <TodoInput add={this.addTodo.bind(this)}></TodoInput>
            <TodoList todos={ this.state.todos }></TodoList>
            <TodoFooter></TodoFooter>
          </article>
        </Provider>
    )
  }

todoItem.jsx:> 消费组件

删除按钮,调到全局共享的删除方法
import { Consumer } from ''
render(){
  return (
    <Consumer>
    ...包起来
      {
        (value)=> { 
          const {删除,切换状态方法} = value
          return(...之前的...) 
        }
      }
    </Consumer>
  )
}
--------------------------------------------------------------
render() {
    const {id,title,completed} = this.props.todo
    return (
      <Consumer>
        {/* Consumer 组件的孩子节点应该是一个函数,函数的 */}
        {/* 参数是在 Provider 组件 value 属性中保存的数据 */}
        {/* Consumer 组件会订阅 context 的变化,即 Provider */}
        {/* 组件 value 属性中保存的值发生变化时,Consumer 组件 */}
        {/* 会自动响应式重新渲染 */}
        { // 此value为provider定义的属性
          (value) => {
            const { toogle, remove } = value
            return (
              <div>
                <a className="panel-block is-active" >
                  <input type="checkbox" onChange={()=>{  toogle(id)} } checked={completed} ></input>
                  <span className="panel-icon">
                    <i className="fas fa-book" aria-hidden="true"></i>
                  </span>
                  {title}--{completed?'已':'未'}完成---{id}
                  注意:这里的{ 里面是个方法调用 }
                  <button onClick={()=>{  remove(id)} }>删除</button>
                </a>
              </div>
            )
          }
        }
      </Consumer>
    )
  }

需要一个函数作为consumer子元素,

函数的参数是在Provider 组件 value 属性中保存的数据,

consum组件会订阅context的变化,即Provider组件 value 属性中保存的值发生变化时,Consumer组件会自动响应式重新渲染

todoFooter:> 类组件 的 contextType 简便的consumer写法

import {TodoContext} from ''
/*class类组件*/
{  
// 定义静态的 contextType,则在成员方法中就可以使用this.context 获取到Provider 组件 value 属性值
  static contextType = TodoContext
    render(){
        const { todos } = this.context // 拿到共享的todos数组
    }
}

课堂案例

随机点名

src/class.jsx

import React, { Component } from 'react'
// 所有人姓名
const _names = ['石雷', '文凯伦', '王君辰', '包旭扬', '侯安康', '王海建', '王万', '王杰', '陈陶', '银登梦', '吕溜洋', '刘茂', '陈鑫荣', '李燕丹', '邓蔚', '何萧', '方炎园', '陈茜', '谢玲星']
export default class ClassComponent extends Component {
  render() {
    return (
      <div>
        <button onClick={this.handleClick}>点名开始</button>
        <button onClick={this.handleEnd}>点名截止</button>
        <div>{this.state.currName}</div>
      </div>
    )
  }
  state = {
    names:_names,
    currName:'',
  }
  /* 开启定时器 */
  handleClick = ()=>{
    this.timer = setInterval(()=>{
      const index = Math.floor(Math.random()*(this.state.names.length))
      this.setState({ 
        currName : this.state.names[index]
      })
    },100)
  }
  /* 停止定时器 */
  handleEnd =()=>{
    clearInterval(this.timer)
  }
}

待办事项

todolist-app/cra4项目模板copy

yarn 直接回车安装依赖

vscode 安装 react 插件 ( ES7+ React/Redux/React-Native snip ) 自动补全代码

src / components/各组件创建

todoHeader.jsx
todoInput.jsx
....

src / index.js

import App from './App'
reactDOM.render(
  <App></App>,
)
import 'bulma/css/min.css'

src / App.jsx ( 最外面的app )

/* 书写组件渲染 */
import React, { Component } from 'react'
import TodoHeader from './components/TodoHeader'
import TodoInput from './components/TodoInput'
import TodoList from './components/TodoList'
import TodoFooter from './components/TodoFooter'
export default class App extends Component {
  render() {
    return (
      <div>
        <TodoHeader></TodoHeader>
        <TodoInput></TodoInput>
        <TodoList></TodoList>
        <TodoFooter></TodoFooter>
      </div>
    )
  }
}

rcc (react class component) class组件快速编写

rfc (react class component) 函数组件

bulma 样式库

npm i bulma

class 是JSX的关键字 最好改成 className

style={ { width:‘100px’ } }

多余父标签不进行渲染,无需向Dom添加额外节点

<React.Fragment></React.Fragment>
===短语法
<></> // 不支持属性添加

Context

Context 提供了一个无需为每层组件手动添加 props,就能在组件树间进行数据传递的方法。 (谨慎使用,会使得组件复用性变差)

利用 Context 可在全局范围内实现组件树中各组件间数据的共享,通常可以 Context 中共享的数据有如:用户登录的状态、地区偏好设置、UI主题设置、语言设置等。

作用:(每个组件拿到都可以改,和vuex不一样)

API

React.createContext()

Provider

Consumer

class.contextType 这是类的静态属性,设置后,可以成员方法中使用 this.context 获取到最近保存在 Provider 组件 value 属性中的值。

条件渲染

jsx内嵌js表达式: 
只能写 { 表达式 } 不可用流程语句
{ // 相比 vue 条件渲染 可读性差 三目运算符
  this.props.todos.length? 满足的:不满足的渲染
}
------------------------------------------------------
 {
          this.props.todos.length >0 ?
          this.props.todos.map(todo => {
            return (
              <TodoItem todo={ todo } key={ todo.id }></TodoItem>
            )
          }):<div>需要先添加待办事件</div>
}

事件处理

React 事件的命名采用小驼峰式(camelCase),而不是纯小写。

使用 JSX 语法时你需要传入一个函数作为事件处理函数,而不是一个字符串。

无修饰符 .enter 键值需要自己去判断

handleAdd(event){
    this.setState({
      inputValue:event.target.value
    })
    if(event.keyCode===13){
      this.props.add(this.state.inputValue)
      event.target.value = ''
    }
  }

传参数

<button onClick={(e) => this.deleteRow(id, e)}>Delete Row</button>
<button onClick={this.deleteRow.bind(this, id)}>Delete Row</button>

表单

vue中的双向绑定 v-model == react实现

表单中内容由 state 控制,不能手动输入 要么设置只读或者绑定onchange事件

区分按键事件还是按钮点击行为,单独定义两个函数

双向绑定实现

<input value={this.state.inputValue}>  </input>
state = {
    inputValue:'',
}
控制台报错:
/*表单中内容由 state 控制,不能手动输入 要么设置只读或者绑定onchange事件*/
-------------------------------------------------------
<input 
    value={this.state.inputValue}
    onChange={this.handleChange.bind(this)}
    ref='inputRef'
/>
 /* 双向绑定输入框 */
  handleChange=(event)=>{
    this.setState({
      inputValue:event.target.value
    })
  }
  /**
   * 点击事件
   */
  handleButton= (event)=>{
    this.props.add(this.state.inputValue)
    // 清空输入框
    this.setState({
      inputValue:''
    })
  }

高阶组件(HOC)复用

Higher Order Component

实现复用组件

是一种基于 React 的组合特性而形成的设计模式。---- 装饰器模式:允许向一个现有的对象添加新的功能,同时又不改变其结构。

高阶组件本质上是一个函数,函数以组件作为参数,返回新组件。

利用高阶组件,可以在现有组件功能基础上增加新的功能,也可以对现有组件进行功能增强。

utils/with-footer 尾部组件复用
/**
 * 高阶组件是一个函数,以组件为参数,返回新的组件
 */
 import React, { Component } from 'react'
export default function withFooter(InComponent){
  /* 定义返回的新组件 函数组件和类组件 */
   class OutComponent extends Component {
    render() {
      return (
        <>
          {/* 传进来的每个组件下面实现复用尾部组件,other可以传参 */}
          <InComponent other="额外的属性"></InComponent>
          <div>尾部链接&copy;&reg;</div>
        </>
      )
    }
  }
  return OutComponent
}
app.jsx
export default withFooter(App)

vueRouter实例 vuex中store 可以看作是单例模式

原型链–原型模式

类组件

方法和初始数据states

/* 类组件-父传子 */
  state = {
    todos:new Array(5).fill(null).map(todo=>{
            return {
              title:'宝宝',
              id:Math.random(),
              completed:false,
            }
          })
  }

生命周期

组件从创建开始,到销毁结束,所经历的整个过程,就是生命周期。

生命周期钩子:在正常的生命周期过程中,有机会执行到用户额外的代码逻辑。

只在 class 组件中有生命周期钩子函数,函数组件中不存在这些生命周期钩子(函数组件中 this 为 undefined)。

挂载阶段

当组件实例被创建并插入 DOM 中时

**constructor():**构造器,初始化,如:state初始化,方法中 this 指向的绑定

static getDerivedStateFromProps():

**render():**渲染

**componentDidMount():**会在组件挂载后(插入 DOM 树中)立即调用。依赖于 DOM 节点的初始化应该放在这里。如需通过网络请求获取数据,此处是实例化请求的好地方。

了解:

componentWillMount():会在 render() 前调用,已废弃

更新阶段

当组件的 props 或 state 发生变化时会触发更新

static getDerivedStateFromProps():

shouldComponentUpdate():通常作为性能优化的方式存在。这个方法会返回 boolean 值,当返回 true 时,会继续向后执行流程,返回 false 时,中断当前阶段渲染。默认行为是 state 每次发生变化组件都会重新渲染。

render():

**componentDidUpdate():**会在更新后会被立即调用 (一般避免在这里进行修改状态数据,可能会进入更新循环 ===== updated)

卸载阶段(销毁)

**componentWillUnmount():**销毁阶段常销毁的资源:打开的连接、启动的定时器、未完成的网络请求、订阅的监听等

函数组件:

方法和初始数据Hooks

Props 通过函数参数传递

什么时候我会用 Hook? 如果你在编写函数组件并意识到需要向其添加一些 state,以前的做法是必须将其转化为 class。现在你可以在现有的函数组件中使用 Hook。

Hooks 作用:【面试】

Hook 是 React 16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。

函数组件 | 自定义的hook中用

Hook是在函数组件中使用的,不能在 class 组件中使用,可以在函数组件中使用到 class 类组件中的特性。

State Hook

在函数组件中使用类似 class 组件的 state 特性:

函数组件的 this 为 undefined

import React,{useState} from 'react'
/* useState初始值 */
const todosInit= new Array(5).fill(null).map(todo=>{
  return {
    title:'宝宝',
    id:Math.random(),
    completed:false,
  }
})
export default function FunctionalApp(){
    const [todos,setTodos] = useState(todosInit)
      const addTodo = title=>{
        setTodos([
          ...todos,
          {
            title,
            id:Math.random(),
            completed:false,
          }
        ])
      }
}

useState() 传递状态初始值作为参数,返回一个数组。数组第一个元素为状态数据,第二个元素是修改状态的方法。

可能是异步的 与 class 中setState特性类似

const [todos,setTodos] = useState(todosInit)

Effect Hook 【面试】

模拟三个 class 组件的钩子

Effect Hook 可以让你在函数组件中执行副作用操作。

如果你熟悉 React class 的生命周期函数,你可以把 useEffect Hook 看做 componentDidMount,componentDidUpdate 和 componentWillUnmount 这三个函数的组合。

React.useEffect(回调函数,可选的依赖项)

  /**
   * Effect Hook
   */
  useEffect(() => {
    console.log('未传递第二个参数...') // 每次组件渲染,那会执行回调函数
  })
  // componentDidMount()
  useEffect(() => {
    console.log('传递第二个参数为空数组...') // 组件挂载执行一次回调函数
    // componentWillUnmount() 面试重点
    return () => {
      console.log('销毁')
    }
  }, [])
  // componentDidMount() + componentDidUpdate()
  useEffect(() => {
    // 组件挂载及 依赖项 更新时执行回调函数
  // 怎么让回调函数中初次不渲染? =》  添加开关 ifelse 判断是否为初次渲染     
    console.log('传递第二个参数为数组,添加 todos 依赖项...')
  }, [todos])

useCallback & useMemo

缓存函数

React Router

官网

V5.x

包资源:

react-router:核心包

react-router-dom:用于DOM处理的包资源

react-router-native:用于原生应用路由切换

react-router-config:路由配置相关的包(renderRoutes()、matchRoutes())

SPA

单页面应用程序

前端路由模式

【面试? 有无自己做过项目部署?了解路由模式】

hash:利用URL中 hash 值变化不会重新请求服务器html资源的特点,在 JS 中监测 hash 值的变化并动态切换对应组件的渲染。

history:利用 H5 中新增的 history API 方法(pushState() / replaceState()),当修改 history 记录时也不会向服务器发送新的网络请求。

安装

$ npm i react-router-dom@5.2.0
# 或
$ yarn add react-router-dom@5.2.0

实例

创建三个页面 16.X版本react

src>views>home.jsx
src>views>about.jsx
src>App.jsx 【最外层盒子】

index.js

/* 引入hashrouter */
import { HashRouter as Router} from 'react-router-dom'
import App from './App'
ReactDOM.render(
  <Router>
     <App/>
  </Router>,
  document.getElementById('root')
)
--------------------------------------------------------------
运行之后
地址栏会有 #
http://localhost:3000/#/

渲染页面

相比 vue 更灵活

import React, { Component } from 'react'
// 引入 link route
import { Link, Route } from 'react-router-dom'
// 页面组件
import Home from './views/home'
import About from './views/about'
import User from './views/user'
// 复用头部导航
import Navbar from './components/nav-bar'
export default class App extends Component {
  render() {
    return (
      <>
        <div>
          <Link to="/home">首页</Link> |
          <Link to="/about">关于</Link> |
          <Link to="/users">用户管理</Link> |
        </div>
    // 三种方法渲染组件-------------
        <Route path="/home" component={Home}/> 
        <Route path="/about" render={()=>{
          // render方法渲染组件
          return (
            <>
              <About></About>
              <div>尾部</div>
            </>
          )
        }}>
        </Route> 
        <Route path="/users">
          {/* 内嵌孩子节点渲染组件 */}
          <Navbar></Navbar>
          <User></User>
        </Route> 
      </>
    )
  }
}

API

HashRouter

hash 路由模式

index.js:
import React from 'react'
import ReactDOM from 'react-dom'
/* 引入hashrouter */
import { HashRouter as Router} from 'react-router-dom'
import App from './App'
ReactDOM.render(
  <Router>
     <App/>
  </Router>,
  document.getElementById('root')
)
App.jsx:
export default class App extends Component {
  render() {
    return (
      <div>App
        <Link to="/home">首页</Link>
        <Link to="/about">关于</Link>
        <Link to="/users">用户管理</Link>
      </div>
    )
  }
}
----------------------------------------------------
根据采用的 hash 路由模式来渲染link标签 地址:#/home

BrowserRouter

history 路由模式

index.js:
import React from 'react'
import ReactDOM from 'react-dom'
/* 引入browserrouter */
import { BrowserRouter as Router} from 'react-router-dom'
import App from './App'
ReactDOM.render(
  <Router>
     <App/>
  </Router>,
  document.getElementById('root')
)
App.jsx:
export default class App extends Component {
  render() {
    return (
      <div>App
        <Link to="/home">首页</Link>
        <Link to="/about">关于</Link>
        <Link to="/users">用户管理</Link>
      </div>
    )
  }
}
----------------------------------------------------
根据采用的 history 路由模式来渲染link标签 地址:没有多余的 # 

Link 链接

App.jsx:
import { Link, Route,Redirect } from 'react-router-dom'
  render() {
    return (
      <div>App
        <Link to="/home">首页</Link>
        <Link to="/about">关于</Link>
        <Link to="/users">用户管理</Link>
      </div>
    )
  }

Route 路由配置

import { Link, Route,Redirect } from 'react-router-dom'
render() {
    return (
        <>
            <Route path="" component={} />
            <Route path="" render={() => {return <></>}} />
            <Route path="">
            {/* 内嵌孩子节点渲染组件 类似vue 中的路由组件children:[]*/}
              <></>
            </Route>
        </>
    )
}

Redirect 重定向

import { Link, Route,Redirect } from 'react-router-dom'
{/* 重定向 */}
render() {
    return (
    <Redirect to="/users"></Redirect>
    )
}

Switch

解决问题:当前页面 /user 刷新当前页之后 跳回重定向设置的 home 页面了

解决以上问题:switch 组件直接包裹路由 | 重定向组件 : 当前页面刷新还是当前页面 选择匹配

return (
    <Switch>
              {/* 路由配置 */}
              <Route path="/home" component={Home}/> 
              <Route path="/about" render={()=>{
                // render方法渲染组件
                return (
                  <>
                    <About></About>
                    <div>尾部</div>
                  </>
                )
              }}>
              </Route> 
              <Route path="/users">
                {/* 内嵌孩子节点渲染组件 */}
                <Navbar></Navbar>
                <User></User>
              </Route> 
              {/* 重定向 */}
              <Redirect to="/users"></Redirect>
    </Switch>
)

嵌套路由

App.js:
<Route path="/users">
            {/* 内嵌孩子节点渲染组件 */}
            <Navbar></Navbar>
            <User></User>
</Route> 
--------------------------------------------------
user.jsx:
import { Link, Route, Switch } from 'react-router-dom'
import UserEdit from './user-edit' // 子路由组件
import UserList from './user-list' // 子路由组件
export default class User extends Component {
  render() {
    return (
      <>
        <div>用户管理</div>
        <div className='left'>
          <Link to="/users/list">查看用户列表</Link>
          <Link to="/users/edit">添加用户信息</Link>
        </div>
        <div className="right">
          右侧内容显示区域
          {/* 嵌套路由 */}
          <Switch>
            <Route path="/users/list" component={UserList}></Route>
            <Route path="/users/edit" component={UserEdit}></Route>
          </Switch>
        </div>
      </>
    )
  }
}

对象=》字符串

属性 - 拿到参数

history 编程式导航

location 参数 location

match 动态路径参数 params

路由传参方式:

querystring : 利用 ? 传递查询字符串参数

path : 路径参数,利用 /:id 来定义参数,传递参数时直接以 / 分隔

Redux :

类似于 vue 中的 vuex

状态管理器

集中式管理应用中的状态数据,以一种可预测的方式更新状态数据。

官网

中文文档

react-redux 绑定库官网

redux 与 react 没有任何直接关系,所以在 react 中要使用 redux 进行状态管理,通常还需要额外的绑定库:react-redux(https://cn.redux.js.org/)

概念

store:仓库,是一个容器,主要用于集中管理应用中的状态

state:状态,是各组件需要共享的状态数据

action:是一个普通对象,通常用于描述发生了什么。这个对象中一般有两个属性:type 和 payload。

action creator:action 创建函数,是一个函数结构,主要用于创建 action 对象,以实现 action 的复用。

reducer:是用于同步更新状态数据的纯函数。该函数以 state 和 action 作为参数,返回新的 state 状态数据。函数签名是:(state, action) => newState。

纯函数:

返回值依赖于参数

函数主体内部不产生任何副作用

类组件的render() 函数应该为纯函数,这意味着在不修改组件 state 的情况下,每次调用时都返回相同的结果,并且它不会直接与浏览器交互

Reducer 必需符合以下规则:

仅使用 state 和 action 参数计算新的状态值

禁止直接修改 state。必须通过复制现有的 state 并对复制的值进行更改的方式来做 不可变更新(immutable updates)。(深克隆操作)

禁止任何异步逻辑、依赖随机值或导致其他“副作用”的代码

reducer 函数内部的逻辑通常遵循以下步骤:

检查 reducer 是否关心这个 action

如果是,则复制 state,使用新值更新 state 副本,然后返回新 state

否则,返回原来的 state 不变

dispatch:是一个函数,传递 action 作为参数,以调用到 reducer 进行状态更新(在组件中不能直接调用 reducer() 进行状态更新,注意:reducer() 函数的调用是封装在 dispatch() 函数体内部实现的。)

selector:是一个函数,用于从 store 的 state 状态树中提取片段。

三大原则

单一数据源

整个应用的 state 被储存在一棵 object tree 中,并且这个 object tree 只存在于唯一一个 store 中。

State 是只读的

唯一改变 state 的方法就是触发 action(即调用 dispatch(action) 方法),action 是一个用于描述已发生事件的普通对象。

使用纯函数来执行修改

为了描述 action 如何改变 state tree ,你需要编写 reducers。

深克隆

json.parse|| json.string

递归

lodash 三方库

lodash文档

yarn add lodash
/**
 * 深克隆-三方库
 */
import _ from 'lodash'
/** 对 state 进行深克隆 */
const copyState = _.cloneDeep(state)

_. 调方法的是 lodash

$. 调方法是jquery

使用:

安装

$ npm i redux@4.1.2 react-redux@7.2.6
# 或
$ yarn add redux@4.1.2 react-redux@7.2.6

创建

创建reducers

创建 ./src/reducers 目录,定义 reducer 函数,如:

购物车状态数据管理:

src/reducers/cart.js

实现购物车状态数据管理

rx 快代码提示快捷输入

/* 实现购物车状态数据管理 
 * rx 代码提示
*/
/**
 * 引入actions type变量
 */
import { ADD_TO_CARTS,REMOVE_FROM_CARTS} from '../actions/constants'
/**
 * 深克隆-三方库
 */
import _ from 'lodash'
/*  
 * 初始状态数据
 * cart购物车数组
*/
const initialState = {
  cart:[],
}
/**
 * reducer 函数,是一个纯函数,用于同步更新状态数据
 * state 只能通过复制处理更新数据
 * @param {*} state 旧状态数据
 * @param {*} action action对象,有 type 和 payload 属性 
 * @returns 返回新的状态数据
 */
const cartReducer = (state = initialState,{type,payload})=>{
  /* 纯函数-避免副作用,所以需要深克隆 */
  const copyState = _.cloneDeep(state)
  /**
   * 使用 switch 多分支选择,来判断当前 action 的动作类型
   * 检查 reducer 是否关心这个 action,否则,返回原来的 state 不变
   * 解决:由于匹配字符串 type 可能会不太方便,用变量来保存
   */
  switch (type) {
    // 添加商品操作'':
    case 'add_to_cart':
      copyState.cart.push(payload)
      return copyState;
    // 添加商品操作:
    case ADD_TO_CARTS:
      copyState.cart.push(payload)
      return copyState;
    // 删除商品操作:
    case REMOVE_FROM_CARTS:
      copyState.cart = copyState.cart.filter(prod=>prod.id!==payload.id)
      return copyState
    default: 
        return state    
  }
}
export default cartReducer

多个reducer 合并成一个 reducer:

src/reducers/index.js

类似 modules 将多个 reducer 合并成一个 reducer

将各独立的 reducer 合并为根 reducer,

redux : conbineReducers方法实现合并

如:

/**
 * 将多个独立的 reducer 合并为一个根 reducer
 */
import { combineReducers } from 'redux'
import cartReducer from './cart'
const rootReducer = combineReducers({
  shoppingCart: cartReducer,// 类似命名空间
})
export default rootReducer

创建actions

创建 ./src/actions 目录,定义 action creator(action创建函数),如:

src/actions

action creator

src/actions/constants.js

action type 取值 :由于匹配字符串可能出现差错,可以用常量储存

export const ADD = Symbol('add_to_cart')

src/actions/cart.js

用于创建添加到购物车时使用的 action 对象

/**
 * 定义 action creator (action创建函数),用于创建 action 对象,以便于复用 action 对象
 */
import { ADD_TO_CART } from "./constants"
/**
 * 用于创建添加到购物车时使用的 action 对象
 */
export const addToCartAction = (product) => {
  return {
    type: ADD_TO_CART,
    payload: product,
  }
}

创建 Store

创建 ./src/store 目录,定义 Store:

createStore 方法

src/store/index.js

/**
 * 创建 Store 仓库
 */
import { createStore } from 'redux'
import rootReducer from '../reducers'
/**
 * 基于根 reducer 来创建 store
 */
const store = createStore(rootReducer)
export default store

--------------------------将store用到react中----------------------

组件中连接 Redux 的 Store

安装绑定库

yarn add react-redux@7.2.6

1

Provider ===二次封装的context provider

src/index.js

import React from 'react'
import ReactDOM from 'react-dom'
/* 引入App */
import App from './App'
/**
 * 引入 react-redux中的provider :二次封装的context provider
 * 让每个react组件都可以使用到store
 */
import { Provider} from 'react-redux'
/* 引入store */
import store from '../src/store'
/**
 * Since any React component in a React Redux app can be connected to the store, most applications will render a <Provider> at the top level, with the entire app’s component tree inside of it.
 */
ReactDOM.render(
  <Provider store={store}><App/></Provider>,
  document.getElementById('root')
)

componnets/list.jsx

component/cart.jsx

创建组件

使用Redux 的 store

1.connect方法 连接store

import { connect } from ‘react-redux’

connect() 函数的主要作用是:在 React 组件中连接 redux 的 store。

connect() 调用后,返回的是 HOC。

connect(mapStateToProps,mapDispatchToprops)

mapStateToProps 是一个函数,可将 store 中的 state 状态数据映射到组件的 props 属性中。

该函数传递 state 作为参数,返回一个普通对象。返回的对象中各属性会被合并到组件的 props 中

mapDispatchToProps 也是一个函数,将 store 的 dispatch API方法映射到组件属性中使用。

该函数传递 dispatch 作为参数,返回一个普通对象,返回的对象中应该是一个个的方法。

/**
 *  action
 */
import { removeFromCartAction } from '../actions/cart'
import React, { Component } from 'react'
/**
 * connect方法
 */
import { connect } from 'react-redux'
/**
 * mapStateToProps 是一个函数,可将 store 中的 state 状态数据映射到组件的 props 属性中。
 * 该函数传递 state 作为参数,返回一个普通对象。返回的对象中各属性会被合并到组件的 props 中。
 * mapStateToProps() 会订阅 Store 的更新,即当 store 有更新时,mapStateToProps() 就会
 * 被调用,即组件会监听到这种更新,然后实现视图的响应式渲染。
 */
const mapStateToProps = state=>{
  return {
    cart:state.shoppCart.cart
  }
}
 /**
 * mapDispatchToProps 也是一个函数,将 store 的 dispatch API方法映射到组件属性中使用。
 * 该函数传递 dispatch 作为参数,返回一个普通对象,返回的对象中应该是一个个的方法。
 * 这些方法内部应该是 dispatch(action) 实现 store 中状态数据的更新,
 * 返回对象中的方法也会被合并到组件的 props 属性中。
 */
const mapDispatchToProps = dispatch =>{
  return {
    remove:id=>{
      const action = removeFromCartAction(id)
      dispatch(action)
    }
  }
}
const hoc =  connect(mapStateToProps, mapDispatchToProps)
class CartClass extends Component {
  render() {
    console.log('connect连接的方法:cart',this.props);
    const {remove,cart} = this.props
    return (
      <div>
         <h2>小兰的购物车:</h2>
      <ul>
        {
          cart.map(prod=>{
            return (
              <li key={prod.id}>
                小兰喜爱的:{prod.title}--{prod.price}--{prod.num}
                <button onClick={()=>{remove(prod.id)}}>不喜欢</button>  
              </li>
            )
          })
        }
      </ul>
      </div>
    )
  }
}
export default hoc(CartClass)

2.函数组件的hooks方法 连接store

useSelector() 【推荐使用函数hooks】

useDispatch()

import { useSelector,useDispatch } from ‘react-redux’

// useSelector => state 获取数组数据

// useDispatch => dispatch 获取方法

src/componrnts/cart.jsx

import React from 'react'
/**
 * useSelector 获取state
 * useDispatch 获取action 
 */
import { useSelector,useDispatch } from 'react-redux'
/**
 * 引入dispatch用的 action
 */
import { removeFromCartAction } from '../actions/cart'
export default function Cart() {
  /**
   * 调用useSelector 的hook函数
   * 从store的state中获取购物车数组数据
   */
  const cart = useSelector(state=>state.shoppCart.cart)
  /**
   * 调用 useDispatch() 的 hook函数,
   * 从 store 中获取dispatch()函数的引用
   */
  const dispatch = useDispatch()
  /**
   * 移除当前商品
   */
  const handleRemove = (id)=>{
      dispatch(removeFromCartAction(id))
  }
  return (
    <div>
      <h2>小兰的购物车:</h2>
      <ul>
        {
          cart.map(prod=>{
            return (
              <li key={prod.id}>
                小兰喜爱的:{prod.title}--{prod.price}--{prod.num}
                <button onClick={handleRemove.bind(this,prod.id)}>不喜欢</button>  
              </li>
            )
          })
        }
      </ul>
    </div>
  )
}

v>

小兰的购物车:

{

cart.map(prod=>{

return (

小兰喜爱的:{prod.title}–{prod.price}–{prod.num}

<button onClick={()=>{remove(prod.id)}}>不喜欢

)

})

}

)

}

}

export default hoc(CartClass)

#### 2.[函数组件的hooks](https://react-redux.js.org/api/hooks)方法 连接store
> useSelector()  【推荐使用函数hooks】
>
> useDispatch() 
>
> import { useSelector,useDispatch } from 'react-redux'
> // useSelector => state 获取数组数据
> // useDispatch => dispatch 获取方法
src/componrnts/cart.jsx
```jsx
import React from 'react'
/**
 * useSelector 获取state
 * useDispatch 获取action 
 */
import { useSelector,useDispatch } from 'react-redux'
/**
 * 引入dispatch用的 action
 */
import { removeFromCartAction } from '../actions/cart'
export default function Cart() {
  /**
   * 调用useSelector 的hook函数
   * 从store的state中获取购物车数组数据
   */
  const cart = useSelector(state=>state.shoppCart.cart)
  /**
   * 调用 useDispatch() 的 hook函数,
   * 从 store 中获取dispatch()函数的引用
   */
  const dispatch = useDispatch()
  /**
   * 移除当前商品
   */
  const handleRemove = (id)=>{
      dispatch(removeFromCartAction(id))
  }
  return (
    <div>
      <h2>小兰的购物车:</h2>
      <ul>
        {
          cart.map(prod=>{
            return (
              <li key={prod.id}>
                小兰喜爱的:{prod.title}--{prod.price}--{prod.num}
                <button onClick={handleRemove.bind(this,prod.id)}>不喜欢</button>  
              </li>
            )
          })
        }
      </ul>
    </div>
  )
}
相关文章
|
编解码 前端开发 JavaScript
React系列---React技术栈一览
React系列---React技术栈一览
1748 0
|
2月前
|
JavaScript 前端开发 算法
React技术栈-虚拟DOM和DOM diff算法
这篇文章介绍了React技术栈中的虚拟DOM和DOM diff算法,并通过一个实际案例展示了如何使用React组件和状态管理来实现动态更新UI。
41 2
|
2月前
|
消息中间件 前端开发
React技术栈-组件间通信的2种方式
本文介绍了React技术栈中组件间通信的两种方式:通过props传递数据和使用消息发布(publish)-订阅(subscribe)机制,并通过实例代码展示了如何使用PubSubJS库实现跨组件通信。
55 11
React技术栈-组件间通信的2种方式
|
2月前
|
前端开发
React技术栈-react使用的Ajax请求库实战案例
这篇文章介绍了在React应用中使用Axios和Fetch库进行Ajax请求的实战案例,展示了如何通过这些库发送GET和POST请求,并处理响应和错误。
56 10
React技术栈-react使用的Ajax请求库实战案例
|
2月前
|
前端开发
React技术栈-react使用的Ajax请求库用户搜索案例
这篇文章展示了一个React技术栈中使用Ajax请求库(如axios)进行用户搜索的实战案例,包括React组件的结构、状态管理以及如何通过Ajax请求获取并展示GitHub用户数据。
33 7
React技术栈-react使用的Ajax请求库用户搜索案例
|
2月前
|
前端开发 NoSQL MongoDB
React技术栈-基于react脚手架编写评论管理案例
这篇文章介绍了在MongoDB中使用sort和投影来对查询结果进行排序和限制返回的字段,通过具体的命令示例展示了如何实现这些操作。
52 6
React技术栈-基于react脚手架编写评论管理案例
|
2月前
|
前端开发 Python
React技术栈-React路由插件之自定义组件标签
关于React技术栈中React路由插件自定义组件标签的教程。
56 4
React技术栈-React路由插件之自定义组件标签
|
2月前
|
前端开发 JavaScript
React技术栈-React UI之ant-design使用入门
关于React技术栈中使用ant-design库的入门教程,包括了创建React应用、搭建开发环境、配置按需加载、编写和运行代码的步骤,以及遇到错误的解决方法。
43 2
React技术栈-React UI之ant-design使用入门
|
2月前
|
前端开发 程序员 API
React技术栈-React路由插件之react-router的基本使用
这篇博客介绍了React路由插件react-router的基本使用,包括其概念、API、以及如何通过实战案例在React应用中实现SPA(单页Web应用)的路由管理。
73 9
|
2月前
|
前端开发 JavaScript
React技术栈-react的脚手架创建应用案例
本文介绍了如何使用React的官方脚手架工具create-react-app快速创建React项目,并展示了项目的目录结构和基本的代码文件,以及如何启动和运行React应用。
33 2