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>尾部链接©®</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> ) }