VSCode插件 - ES7 React/Redux/GraphQL/React-Native snippets

1️⃣ React 的特点

Facebook 开源的一个 js 库
一个用来动态构建用户界面的 js 库
React的特点

  1. 1. Declarative (声明式编码)
  2. 2. Component-Based (组件化编码)
  3. 3. Learn Once, Write Anywhere (支持客户端与服务器渲染)
  4. 4. 高效
  5. 5. 单向数据流

React高效的原因

  1. 1. 虚拟(virtual)DOM, 不总是直接操作DOM(批量更新, 减少更新的次数)
  2. 2. 高效的DOM Diff算法, 最小化页面重绘(减小页面更新的区域) diff重绘的最小单位是一个标签

1️⃣ React 的基本使用

2️⃣ 引入 JS

  1. <!-- 有引入的先后顺序 -->
  2. <!-- 引入 react 核心库 -->
  3. <script type="text/javascript" src="../js/react.development.js"></script>
  4. <!-- 引入 react-dom 用于支持 react 操作 dom -->
  5. <script type="text/javascript" src="../js/react-dom.development.js"></script>
  6. <!-- 引入 babel 用于将 jsx 转为 js -->
  7. <script type="text/javascript" src="../js/babel.min.js"></script>
  8. // babel.js的作用
  9. // 浏览器不能直接解析JSX代码, 需要babel转译为纯JS的代码才能运行
  10. // 只要用了JSX,都要加上type="text/babel", 声明需要babel来处理

2️⃣ 虚拟 DOM 以及创建

React 提供了一些 API 来创建一种 特别 的一般js对象
虚拟 DOM 对象最终都会被 React 转换为真实的 DOM
我们编码时基本只需要操作 react 的虚拟 DOM 相关数据, react 会转换为真实 DOM 变化而更新界面

3️⃣ 虚拟 DOM 和真实 DOM

虚拟 DOM 较轻, 为一般的 Object 对象, 自身没有属性
真实 DOM 较重, 自身很多属性

3️⃣ 创建虚拟 DOM

  1. <!-- 此处一定要声明是 babel 写的是 jsx 语法由 babel 转换语法 -->
  2. <script type="text/babel">
  3. let myId='demo'
  4. let myData = 'Hello - World'
  5. // 1. 创建虚拟 DOM - 此处不能写引号, 因为不是字符串, 是虚拟 DOM
  6. const VDOM = (
  7. <h1 id={myId}>
  8. <span>
  9. {myData}
  10. </span>
  11. </h1>);
  12. // 2. 渲染虚拟 DOM 到页面
  13. ReactDOM.render(VDOM,document.getElementById('test'));
  14. </script>

2️⃣ JSX

全称: JavaScript XML
react 定义的一种类似于 XML 的 JS 扩展语法:XML+JS
本质是 React.createElement(component, props, …children) 方法的语法糖
作用: 用来简化创建 react 虚拟 DOM (元素)对象
标签名任意: HTML 标签或其它标签
标签属性任意: HTML 标签属性或其它

3️⃣ JSX语法注意点

如要要使用 JSX(符合XML规范的JS语法) 语法,必须先运行 cnpm i babel-preset-react -D,然后再 .babelrc 中添加 语法配置(在presets中加入一个”react“);
JSX语法的本质:JSX内部在运行的时候,也是先把 类似与HTML这样的标签代码转换为 React.createElement的形式来实现的,并没有直接把 用户写的 HTML代码,渲染到页面上;
如果要在JSX语法内部,书写 JS代码,那么,所有的JS代码,必须写到 {} 内部;
当编译引擎,在编译JSX代码的时候,如果遇到了<(尖括号)那么就把它当作HTML代码去编译,如果遇到了 {} (大括号)就把 花括号内部的代码当作 普通JS代码去编译;
{}内部,可以写任何符合JS规范的代码;只可以写 JS 表达式, 不可以写 JS 语句
JSX中,如果要为元素添加class属性了,那么,必须写成className,因为 classES6中是一个关键字;和class类似,label标签的 for 属性需要替换为 htmlFor.
JSX创建DOM的时候,所有的节点,必须有唯一的根元素进行包裹;
如果要写注释了,注释必须放到{}内部(在{}中的注释最好是使用/**/的形式)

  1. const VDOM = (
  2. <h1 id={myId}>
  3. <span className='cspan'>
  4. {/* JSX 注释*/},
  5. {myData}
  6. </span>
  7. </h1>);
  8. const VDOM = <h1>最简单的虚拟 DOM</h1>


2️⃣ 模块与组件和模块化与组件化的理解

3️⃣ 模块

理解: 向外提供特定功能的 js 程序, 一般就是一个js文件
为什么: js代码更多更复杂
作用: 复用 js, 简化 js 的编写, 提高js运行效率

3️⃣ 组件

理解: 用来实现特定 (局部) 资源的集合 (html/css/js/image) 一个界面的功能更复杂
为什么: 一个界面的功能更复杂
作用: 复用编码, 简化项目编码, 提高运行效率

3️⃣ 模块化

当应用的 js 都以模块来编写的, 这个应用就是一个模块化的应用

3️⃣ 组件化

当应用是以多组件的方式实现, 这个应用就是一个组件化的应

2️⃣ 基本理解和使用

3️⃣ 自定义组件 ( Component )

渲染组件标签

  1. 1. ReactDOM.render(<MyComp />, cotainerEle)
  2. 1. ReactDOM.render()渲染组件标签的基本流程
  3. 1. React内部会创建组件实例对象/调用组件函数, 得到虚拟DOM对象
  4. 2. 将虚拟DOM并解析为真实DOM
  5. 3. 插入到指定的页面元素内部

4️⃣ 工厂函数创建组件 - 无状态 / 简单组件

  1. <script type="text/babel">
  2. // 如果渲染标签时, 首字母是小写的, 尝试着寻找 html 与之对应的标签进行渲染, 如果未找到报错
  3. // 如果渲染标签时, 首字母是大写的, 尝试着寻找组件是否定义, 定义了进行组件渲染, 未定义报为未定义错误
  4. // --------------- 使用工厂函数创建组件 ---------------
  5. function MyComponent1(){
  6. console.log(this); // 工厂函数中的 this 指向 undefined
  7. return <h2>使用工厂函数创建组件( 一些简单组件使用工厂函数创建 )</h2>
  8. }
  9. // 下面代码的执行过程
  10. // 1. 寻找 Component 是否定义
  11. // 2. 如果定义了, 调用与组件标签同名的工厂函数, 返回值渲染
  12. ReactDOM.render(<MyComponent1/>,document.getElementById('one'))
  13. </script>

4️⃣ ES6 类组件 - 有状态 / 复杂组件

  1. <script type="text/babel">
  2. // 如果渲染标签时, 首字母是小写的, 尝试着寻找 html 与之对应的标签进行渲染, 如果未找到报错
  3. // 如果渲染标签时, 首字母是大写的, 尝试着寻找组件是否定义, 定义了进行组件渲染, 未定义报为未定义错误
  4. // --------------- ES6类创建组件 ---------------
  5. class MyComponent2 extends React.Component {
  6. render () { // 重写父类继承的方法
  7. console.log(this) // ES6 组件中 render 方法里的 this 是组件类的实例对象
  8. return <h2>ES6类组件( 一些复杂组件用ES6创建组件 )</h2>
  9. }
  10. }
  11. // 下面代码的执行过程
  12. // 1. 创建了 MyComponent2 的实例
  13. // 2. 通过该实例调用到了 render, 从而获取了返回值
  14. ReactDOM.render(<MyComponent2/>,document.getElementById('two'))
  15. </script>

2️⃣ 组件的三大属性

3️⃣ start

组件被称为”状态机”, 页面的显示是根据组件的state属性的数据来显示
state是组件对象最重要的属性, 值是对象 ( 可以包含多个 key-value 的组合 )
组件被称为 “状态机”, 通过更新组件的 state 来更新对应的页面显示 ( 重新渲染组件 )
组件实例对象的 state

  1. 1. 不能直接修改
  2. 2. 不能直接更新

组件实例对象的 state 修改和更新提供了 setState() 的方法
初始化指定:

  1. constructor() {
  2. super()
  3. this.state = {
  4. stateName1 : stateValue1,
  5. stateName2 : stateValue2
  6. }
  7. }

读取显示:

  1. this.state.stateName1

更新状态 —> 更新界面 :

  1. this.setState({stateName1 : newValue})

例子

  1. <script type="text/babel">
  2. class Test extends React.Component {
  3. constructor(a){
  4. // 构造器里的 this 是组件类的实例对象
  5. super()
  6. this.state = {
  7. is:true
  8. }
  9. this.demo = this.demo.bind(this)
  10. }
  11. demo(){
  12. // demo 里的 this 是 undefined
  13. // 在组件列中, 所有自定义的方法中 this 都是 undefined
  14. // 组件实例对象的 state
  15. // 1. 不能直接修改
  16. // 2. 不能直接更新
  17. let {is} = this.state
  18. // 组件实例对象的 state 修改和更新提供了 setState() 的方法
  19. this.setState({is : !is})
  20. /*
  21. 简写形式
  22. let is = !this.state.is
  23. this.setState({is})
  24. */
  25. }
  26. render () {
  27. // render 里的 this 是组件类的实例对象
  28. let {is}=this.state
  29. return <h2 onClick={this.demo}>一句话 : {is ? '愿你历尽千帆' : '归来仍是少年'}</h2>
  30. }
  31. }
  32. ReactDOM.render(<Test/>,document.getElementById('one'))
  33. </script>
  34. // 精简版
  35. <script type="text/babel">
  36. class Test extends React.Component {
  37. state = { is : true }
  38. demo = () => {
  39. let is = !this.state.is
  40. this.setState({is})
  41. }
  42. render () {
  43. let {is} = this.state
  44. return <h2 onClick = {this.demo}>一句话 : {is ? '愿你历尽千帆' : '归来仍是少年'}</h2>
  45. }
  46. }
  47. ReactDOM.render(<Test/>,document.getElementById('one'))
  48. </script>

3️⃣ props

所有组件标签的属性的集合对象
每个组件对象都会有props(properties的简写)属性
组件标签的所有属性都保存在props中
用于限制传递标签属性的类型和必要性

  1. 1. 限制传递标签的类型:ropTypes
  2. 2. 限制传递标签的默认值:defaultProps

扩展属性:将对象的所有属性通过props传递

  1. <Person {...person}/>

例子

  1. <script type="text/babel">
  2. /* 复杂方式 */
  3. class Test extends React.Component {
  4. render () {
  5. let {name,age,sex}=this.props
  6. return(
  7. <ul>
  8. <li>{name}</li>
  9. <li>{age}</li>
  10. <li>{sex}</li>
  11. </ul>
  12. )
  13. }
  14. }
  15. // 限制传递标签的类型
  16. Test.propTypes={
  17. name:PropTypes.string.isRequired,
  18. age:PropTypes.number,
  19. sex:PropTypes.string
  20. }
  21. // 限制传递标签的默认值
  22. Test.defaultProps={
  23. age:18,
  24. sex:"男"
  25. }
  26. let obj = {
  27. name : "chen",
  28. age : 25,
  29. sex : "男"
  30. }
  31. ReactDOM.render(<Test {...obj}/>,document.getElementById('one'))
  32. ReactDOM.render(<Test name="chen"/>,document.getElementById('two'))
  33. // --------------- 简写方式 ---------------
  34. /*
  35. class Test extends React.Component {
  36. // 限制传递标签的类型
  37. static propTypes={
  38. name:PropTypes.string.isRequired,
  39. age:PropTypes.number,
  40. sex:PropTypes.string
  41. }
  42. // 限制传递标签的默认值
  43. static defaultProps={
  44. age:18,
  45. sex:"男"
  46. }
  47. render () {
  48. let {name,age,sex}=this.props
  49. return(
  50. <ul>
  51. <li>{name}</li>
  52. <li>{age}</li>
  53. <li>{sex}</li>
  54. </ul>
  55. )
  56. }
  57. }
  58. let obj = {
  59. name : "chen",
  60. age : 25,
  61. sex : "男"
  62. }
  63. ReactDOM.render(<Test {...obj} />,document.getElementById('one'))
  64. ReactDOM.render(<Test name="hua" />,document.getElementById('two'))
  65. */
  66. </script>

3️⃣ refs 与事件处理

组件内包含ref属性的标签元素的集合对象
给操作目标标签指定ref属性, 打一个标识
在组件内部获得标签对象:this.refs.refName(只是得到了标签元素对象)
作用:找到组件内部的真实dom元素对象, 进而操作它

4️⃣ refs 与事件处理

  1. <script type="text/babel">
  2. class Test extends React.Component{
  3. // 方法一: 获取 input.value
  4. cli = () => {
  5. console.log(this.refs.input1.value);
  6. }
  7. // 方法二: 获取 input.value
  8. blu = (event) => {
  9. console.log(event.target.value);
  10. }
  11. render(){
  12. return(
  13. <div>
  14. {/* ref 与事件处理 */}
  15. <input type="text" ref="input1"/>
  16. <button onClick={this.cli}>点击提醒</button>
  17. <input type="text" placeholder="失去焦点提示" onBlur={this.blu}/>
  18. </div>
  19. )
  20. }
  21. }
  22. ReactDOM.render(<Test />,document.getElementById('one'))
  23. </script>

4️⃣ 新语法 - 回调 ref

  1. <script type="text/babel">
  2. class Test extends React.Component{
  3. // 回调 ref 获取 value
  4. cli = () => {
  5. console.log(this.inp.value);
  6. }
  7. blu = (event) => {
  8. console.log(event.target.value);
  9. }
  10. render(){
  11. return(
  12. <div>
  13. {/* 回调 ref */}
  14. <input type="text" ref={(input) => {this.inp = input}}/>
  15. <button onClick={this.cli}>点击提醒</button>
  16. <input type="text" placeholder="失去焦点提示" onBlur={this.blu}/>
  17. </div>
  18. )
  19. }
  20. }
  21. ReactDOM.render(<Test />,document.getElementById('one'))
  22. </script>

4️⃣ 新语法 - createRef

  1. <script type="text/babel">
  2. class Test extends React.Component{
  3. // 调用 React.createRef 创建一个用于保存唯一 ref 的容器 , 该容器 "专人专用" 只能保存一个 ref
  4. myRef = React.createRef() // 返回值是一个 - Object 类型的 js 对象
  5. // React.createRef 方式获取 value
  6. cli = () => {
  7. console.log(this.myRef.current.value);
  8. }
  9. blu = (event) => {
  10. console.log(event.target.value);
  11. }
  12. render(){
  13. return(
  14. <div>
  15. {/* createRef */}
  16. <input type="text" ref={this.myRef}/>
  17. <button onClick={this.cli}>点击提醒</button>
  18. <input type="text" placeholder="失去焦点提示" onBlur={this.blu}/>
  19. </div>
  20. )
  21. }
  22. }
  23. ReactDOM.render(<Test />,document.getElementById('one'))
  24. </script>

2️⃣ 功能界面的组件化编码流程( 通用 / 无比重要 )

拆分组件:拆分界面,抽取组件
实现静态组件:使用组件实现静态页面效果
实现动态组件

  1. 1. 动态显示初始化数据
  2. 1. 数据类型
  3. 2. 数据名称
  4. 3. 保存在哪个组件
  5. 2. 交互 ( 从绑定事件监听开始 )

例子 - 输入内容添加在下方列表

2️⃣ 组件的组合使用 - 例子

image.png

  1. <script type="text/babel">
  2. // App 应用组件 - 最外层组件的外壳
  3. class App extends React.Component{
  4. state = {
  5. Arr:['ONE','TWO','THREE']
  6. }
  7. addTodo = (data) => {
  8. // 获取原状态中的数组
  9. let {Arr} = this.state
  10. // 像数组中添加数据
  11. Arr.unshift(data)
  12. // 更新状态
  13. this.setState({Arr:Arr})
  14. }
  15. render(){
  16. let {Arr} = this.state
  17. let addTodo = this.addTodo
  18. return(
  19. <div>
  20. <h1>简单的列表展示</h1>
  21. <Add addTodo={addTodo} len={Arr.length} />
  22. <List Arr={Arr} />
  23. </div>
  24. )
  25. }
  26. }
  27. // Add 组件 - 用于添加一条数据
  28. class Add extends React.Component{
  29. add = () => {
  30. // 1. 获取用户输入
  31. let userInput = this.input.value
  32. // 2. 校验数据
  33. // trim() 方法去除字符串的头尾空格
  34. if(userInput.trim() === "" ) return;
  35. // 3. 将用户的输入添加到状态里
  36. this.props.addTodo(userInput)
  37. // 4. 清空输入框
  38. this.input.value = ''
  39. }
  40. render(){
  41. let {len} = this.props
  42. return(
  43. <div>
  44. <input type="text" ref={(input) => {this.input=input}}/>
  45. <button onClick={this.add}><span>Add - {len}</span></button>
  46. </div>
  47. )
  48. }
  49. }
  50. // List 组件 - 用于展示列表
  51. class List extends React.Component{
  52. render(){
  53. let {Arr} = this.props
  54. return (
  55. <ul>
  56. {
  57. Arr.map((item,index) => <li key={index}>{item}</li>)
  58. }
  59. </ul>
  60. )
  61. }
  62. }
  63. ReactDOM.render(<App/>,document.getElementById('one'))
  64. </script>

2️⃣ 受控组件与非受控组件

受 state 控制状态的组件是受控组件否则不是

  1. <script type="text/babel">
  2. // 要求
  3. // 用户名在提交时获取
  4. // 密码在用户输入改变时获取, 保存在状态中, 提交时获取状态中保存的密码
  5. class Login extends React.Component{
  6. // 初始化状态
  7. state = {
  8. password:''
  9. }
  10. submitC = (event) => {
  11. var e = event || window.event;
  12. // 阻止默认事件
  13. e.preventDefault();
  14. let {username} = this.refs
  15. let {password} = this.state
  16. // 弹窗提示用户的输入
  17. console.log(`用户名: ${username.value} - 密码: ${password}`);
  18. }
  19. changeC = (event) => {
  20. console.log(event.target.value);
  21. this.setState({password:event.target.value})
  22. }
  23. render(){
  24. return(
  25. <form action="https://www.baidu.com" onSubmit={this.submitC}>
  26. 用户名: <input type="text" ref="username"/>
  27. 密码: <input type="password" onChange={this.changeC}/>
  28. <input type="submit" value="登录"/>
  29. </form>
  30. )
  31. }
  32. }
  33. ReactDOM.render(<Login/>,document.getElementById('one'))
  34. </script>

2️⃣ 纯函数和高阶函数与函数柯里化

高阶函数

  1. 1. 如果一个函数符合下面两个规范中的任何一个, 那么函数就是高阶函数
  2. 1. 如果 A 函数接收的参数是一个函数, 那么 A 函数就可以称之为高阶函数
  3. 2. 如果 A 函数调用的返回值依然是一个函数, 那么 A 函数就可以称之为高阶函数

函数柯里化
通过函数调用继续返回函数的方式, 实现多次接收参数最后统一处理的函数编码形式

  1. // 柯里化 - 例子
  2. function sum(a) {
  3. return (b) => {
  4. return (c) => {
  5. return a + b + c;
  6. }
  7. }
  8. }
  9. console.log(sum(1)(2)(3));
  1. // 高阶函数与函数柯里化
  2. <script type="text/babel">
  3. class Login extends React.Component{
  4. // 初始化状态
  5. state = {
  6. password:'',
  7. username:''
  8. }
  9. submitC = (event) => {
  10. var e = event || window.event;
  11. // 阻止默认事件
  12. e.preventDefault();
  13. let {username} = this.refs
  14. let {password} = this.state
  15. // 弹窗提示用户的输入
  16. console.log(`用户名: ${username.value} - 密码: ${password}`);
  17. }
  18. // 高阶函数 函数柯里化
  19. changeData = (data) => {
  20. return (event) => {
  21. console.log(event.target.value);
  22. this.setState({[data]:event.target.value})
  23. }
  24. }
  25. render(){
  26. return(
  27. <form action="https://www.baidu.com" onSubmit={this.submitC}>
  28. 用户名: <input onChange={this.changeData('username')} type="text" ref="username"
  29. 密码: <input onChange={this.changeData('password')} type="password" />
  30. <input type="submit" value="登录"/>
  31. </form>
  32. )
  33. }
  34. }
  35. ReactDOM.render(<Login/>,document.getElementById('one'))
  36. </script>

2️⃣ 组件生命周期

链接:https://projects.wojtekmaj.pl/react-lifecycle-methods-diagram/

3️⃣ 组件声明周期 - 旧版

image.png

image.png

4️⃣ 组件的三个生命周期状态

Mount: 插入真实 DOM
Update: 被重新渲染
Unmount: 被移出真实 DOM

4️⃣ 生命周期流程

第一次初始化显示:ReactDOM.render( , containDom)

  1. 1. constructor()
  2. 2. componentWillMount():将要插入回调 - 组件将要挂载
  3. 3. render():用于插入虚拟DOM回调
  4. 4. componentDidMount():已经插入回调 - 组件挂载完成

每次更新 state:this.setState({})

  1. 1. componentWillReceiveProps():接收父组件新的属性
  2. 2. componentWillUpdate():将要更新回调
  3. 3. render():更新(重新渲染)
  4. 4. componentDidUpdate():已经更新回调

删除组件:ReactDOM.unmountComponentAtNode(div):移除组件
componentWillUnmount():组件将要被移除回调

4️⃣ 常用的方法

render():必须重写, 返回一个自定义的虚拟DOM
constructor():初始化状态(state = {}), 绑定this(可以箭头函数代替)
componentDidMount():只执行一次, 已经在dom树中, 适合启动 / 设置一些监听

3️⃣ 组件声明周期 - 新版

新版即将废弃

  1. 1. componentWillMount()
  2. 2. componentWillReceiveProps()
  3. 3. componentWillUpdate()

新增的钩子

  1. 1. static getDerivedStateFromProps() 相当于之前的 componentWillMount() componentWillUpdate()
  2. 2. getSnapshotBeforeUpdate()

image.png

4️⃣ 组件的三个生命周期状态:

初始化阶段:第一次渲染,插入真实DOM
更新阶段:重新渲染
卸载阶段:被移出真实 DOM

4️⃣ 生命周期流程:

初始化阶段: 由ReactDOM.render()触发

  1. 1. constructor()
  2. 2. static getDerivedStateFromProps()
  3. 3. render()
  4. 4. componentDidMount()

更新阶段 由组件内部this.setSate()或父组件重新render触发

  1. 1. static getDerivedStateFromProps()
  2. 2. shouldComponentUpdate()
  3. 3. render()
  4. 4. getSnapshotBeforeUpdate()
  5. 5. componentDidUpdate()

移除组件: 由ReactDOM.unmountComponentAtNode(containerDom)触发
componentWillUnmount()

4️⃣ 重要的勾子

render():初始化渲染或更新渲染调用
componentDidMount():开启监听, 发送ajax请求
componentWillUnmount():做一些收尾工作, 如:清理定时器
static getDerivedStateFromProps():state是根据props来生成的那就需要借助这个函数

2️⃣ 虚拟DOM与DOM diff算法

3️⃣ 虚拟DOM是什么?

一个虚拟DOM(元素)是一个一般的js对象, 准确的说是一个对象树(倒立的)
虚拟DOM保存了真实DOM的层次关系和一些基本属性,与真实DOM一一对应
如果只是更新虚拟DOM, 页面是不会重绘的

3️⃣ Virtual DOM 算法的基本步骤

用JS对象树表示DOM树的结构;然后用这个树构建一个真正的DOM树插到文档当中
当状态变更的时候,重新构造一棵新的对象树。然后用新的树和旧的树进行比较,记录两棵树差异
把差异应用到真实DOM树上,视图就更新了

3️⃣ 进一步理解

Virtual DOM 本质上就是在 JS 和 DOM 之间做了一个缓存。
可以类比 CPU 和硬盘,既然硬盘这么慢,我们就在它们之间加个缓存:既然 DOM 这么慢,我们就在它们 JS 和 DOM 之间加个缓存。CPU(JS)只操作内存(Virtual DOM),最后的时候再把变更写入硬盘(DOM)。

2️⃣ Reack 中的 key

3️⃣ 虚拟 DOM 的 key 的作用?

简单的说:key是虚拟DOM对象的标识, 在更新显示时key起着极其重要的作用
详细的说:当列表数组中的数据发生变化生成新的虚拟DOM后, React进行新旧虚拟DOM的diff比较

  1. 1. key 没有变
  2. 1. 对应item数据没变, 直接使用原来的真实DOM
  3. 2. 对应item数据变了, 对原来的真实DOM进行数据更新
  4. 2. key 变了
  5. 1. key变化:销毁原来的真实DOM, 根据item数据创建新的虚拟DOM,随后渲染真实DOM到页面(即使item数据没有变)
  6. 2. key产生:根据item数据创建新的虚拟DOM,随后渲染真实DOM到页面

3️⃣ key 为 index 的问题

添加/删除/排序 => 产生没有必要的真实DOM更新 ==> 界面效果没问题, 但效率低
如果item界面还有输入框 => 产生错误的真实DOM更新 ==> 界面有问题
注意:如果不存在添加/删除/排序操作,仅用于渲染列表用于展示,使用index作为key没有问题。

3️⃣ 解决

使用item数据的标识数据作为key, 比如id属性值

1️⃣ React 应用 - 基于脚手架

2️⃣ react脚手架

xxx脚手架:用来帮助程序员快速创建一个基于xxx库的模板项目

  1. 1. 包含了所有需要的配置
  2. 2. 指定好了所有的依赖
  3. 3. 可以直接安装/编译/运行一个简单效果

react提供了一个用于创建react项目的脚手架库:create-react-app
项目的整体技术架构为: react + webpack + es6 + eslint
使用脚手架开发的项目的特点:模块化, 组件化, 工程化

2️⃣ 创建项目并启动

链接:https://create-react-app.dev/docs/getting-started/
安装脚手架
npm install -g create-react-app
创建项目

  1. 1. yarn create react-app my-app
  2. 1. my-app 为项目名称

进入项目文件
cd hello-react
启动项目

  1. 1. npm start
  2. 1. 注意: 如果出现版本兼容问题请这样解决
  3. 1. 链接:[https://www.cnblogs.com/donghezi/p/10234721.html](https://www.cnblogs.com/donghezi/p/10234721.html)

2️⃣ react脚手架项目结构

ReactNews
|—node_module———第三方依赖模块文件夹
|—public
|— index.htm———主页面
|—src———源码文件夹
|—components———react组件
|—index.js———应用入口js
|—.gitignore———git版本管制忽略的配置
|—package.json———应用包配置文件
|—README.md———应用描述说明的readme文件

1️⃣ React ajax

2️⃣ 前置说明

React本身只关注于界面, 并不包含发送ajax请求的代码
前端应用需要通过ajax请求与后台进行交互(json数据)
react应用中需要集成第三方ajax库(或自己封装)

2️⃣ 常用的ajax请求库

jQuery 比较重, 如果需要另外引入不建议使用
axios 轻量级, 建议使用

  1. 1. 封装XmlHttpRequest对象的ajax
  2. 2. promise风格
  3. 3. 可以用在浏览器端和node服务器端

fetch 原生函数, 但老版本浏览器不支持

  1. 1. 不再使用XmlHttpRequest对象提交ajax请求
  2. 2. 为了兼容低版本的浏览器, 可以引入兼容库fetch.js

2️⃣ react脚手架配置代理总结

3️⃣ 方法一

在package.json中追加如下配置

  1. "proxy":"http://localhost:5000"

说明:

  1. 优点:配置简单,前端请求资源时可以不加任何前缀。
  2. 缺点:不能配置多个代理。
  3. 工作方式:上述方式配置代理,当请求了3000不存在的资源时,那么该请求会转发给5000 (优先匹配前端资源)

3️⃣ 方法二

  1. 第一步:创建代理配置文件

    1. src下创建配置文件:src/setupProxy.js
  2. 编写setupProxy.js配置具体代理规则: ```javascript const proxy = require(‘http-proxy-middleware’)

module.exports = function(app) { app.use( proxy(‘/api1’, { //api1是需要转发的请求(所有带有/api1前缀的请求都会转发给5000) target: ‘http://localhost:5000‘, //配置转发目标地址(能返回数据的服务器地址) changeOrigin: true, //控制服务器接收到的请求头中host字段的值 / changeOrigin设置为true时,服务器收到的请求头中的host为:localhost:5000 changeOrigin设置为false时,服务器收到的请求头中的host为:localhost:3000 changeOrigin默认值为false,但我们一般将changeOrigin值设为true / pathRewrite: {‘^/api1’: ‘’} //去除请求前缀,保证交给后台服务器的是正常请求地址(必须配置) }), proxy(‘/api2’, { target: ‘http://localhost:5001‘, changeOrigin: true, pathRewrite: {‘^/api2’: ‘’} }) ) }

  1. 说明:
  2. 1. 优点:可以配置多个代理,可以灵活的控制请求是否走代理。
  3. 2. 缺点:配置繁琐,前端请求资源时必须加前缀。
  4. <a name="RwFho"></a>
  5. # 1️⃣ React 组件通讯
  6. <a name="wBGxG"></a>
  7. ## 2️⃣ 父子组件通讯
  8. ```javascript
  9. // 父组件
  10. import React, { Component } from "react";
  11. import Son from "./son";
  12. export default class App extends Component {
  13. state = {
  14. states: ["a", "b", "c"],
  15. };
  16. upDate = (data) => {
  17. let up = this.state.states.push(data);
  18. this.retState({ states: up });
  19. };
  20. render() {
  21. return (
  22. <div>
  23. {/* 父组件向子组件通讯 */}
  24. <Son upDate={this.upDate} />
  25. </div>
  26. );
  27. }
  28. }
  29. // 子组件
  30. import React, { Component } from "react";
  31. export default class Son extends Component {
  32. sonDate = (data) => {
  33. // 子组件调用父组件传过来的方法调用传参, 实现逆向通讯
  34. this.props.upDate("d", "e");
  35. };
  36. render() {
  37. return <div></div>;
  38. }
  39. }

2️⃣ 兄弟组件通讯 - 消息订阅与发布机制

链接:https://github.com/mroderick/PubSubJS
工具库:PubSubJS
下载:npm install pubsub-js —save
使用:

  1. 1. import PubSub from 'pubsub-js' // 引入
  2. 2. PubSub.subscribe('delete', function(data){ }); // 订阅
  3. 3. PubSub.publish('delete', data) // 发布消息
  4. 4. PubSub.unsubscribe() // 取消订阅
  1. // A 组件发布消息
  2. // A 组件发布消息 消息名为 A 数据为 {name:'chen',age:'25' }
  3. PubSub.publish('A', {name:'chen',age:'25' })
  4. // B 组件订阅消息
  5. // B 组件订阅消息名为 A 的消息并读取发布的数据
  6. PubSub.subscribe('A', (msg, data) => { console.log(data); })
  7. // 取消订阅
  8. PubSub.publish('A', {name:'chen',age:'25' })
  9. this.cancel = PubSub.subscribe('A', (msg, data) => { console.log(data); })
  10. PubSub.unsubscribe(this.cancel) // 取消订阅

1️⃣ React 路由

链接:https://react-router.docschina.org/

2️⃣ SPA的理解

单页Web应用(single page web application,SPA)。
整个应用只有一个完整的页面。
点击页面中的链接不会刷新页面,只会做页面的局部更新。
数据都需要通过 ajax 请求获取, 并在前端异步展现。

2️⃣ 路由的理解

3️⃣ 什么是路由?

一个路由就是一个映射关系 ( key :value )
key 为路径, value 可能是 function 或 component

3️⃣ 路由分类

后端路由:

  1. 1. 理解: valuefunction, 用来处理客户端提交的请求。
  2. 2. 注册路由: router.get(path, function(req, res))
  3. 3. 工作过程:当node接收到一个请求时, 根据请求路径找到匹配的路由, 调用路由中的函数来处理请求, 返回响应数据

前端路由:

  1. 1. 浏览器端路由,value component,用于展示页面内容。
  2. 2. 注册路由: <Route path="/test" component={Test}>
  3. 3. 工作过程:当浏览器的path变为/test时, 当前路由组件就会变为Test组件

2️⃣ 路由的基本使用

下载依赖包
npm install —save react-router-dom
引入 ( react-router-dom 是分别暴露, 用到哪一个解构赋值哪一个 )
import { Link, Route, …… } from “react-router-dom”;
明确好界面中的导航区、展示区
导航区的a标签改为Link标签
Demo
展示区写Route标签进行路径的匹配

的最外侧包裹了一个

2️⃣ react-router-dom 的理解

react的一个插件库。
专门用来实现一个SPA应用。
基于react的项目基本都会用到此库。

3️⃣ react-router-dom 相关 API


用于包裹路由的标签, 如果项目中需要用到路由, 一般包裹在 标签外

  1. // 渲染 App 到页面
  2. ReactDOM.render(
  3. <BrowserRouter>
  4. <App />
  5. </BrowserRouter>,
  6. document.getElementById("root")
  7. );


用于包裹路由的标签, 如果项目中需要用到路由, 一般包裹在 标签外

  1. // 渲染 App 到页面
  2. ReactDOM.render(
  3. <HashRouter>
  4. <App />
  5. </HashRouter>,
  6. document.getElementById("root")
  7. );
  1. 1. 用于匹配路由, 匹配到指定路由时调用指定组件
  2. 1. 注意:多级路由时可能会出现样式丢失问题解决方法如下三种
  3. 1. 删除样式前的点
  4. 1. <link rel="stylesheet" href="/css/reset.css" />
  5. 2. 样式前的点用 %PUBLIC_URL% 代替, 指带绝对路径下的路径
  6. 1. <link rel="stylesheet" href="%PUBLIC_URL%/css/reset.css" />
  7. 3. 使用 <HashRouter>
  8. 1. ReactDOM.render(<HashRouter><App /></HashRouter>,document.getElementById("root"));
  1. // 如果不指定 path 操作时会每次都路过这个路径
  2. <Route path="/about" component={About} />


在初始页面时指定最开始展示的组件页面 - 重定向

  1. <Switch>
  2. <Route path="/about" component={About} />
  3. <Route path="/home" component={Home} />
  4. <Route path="/Test" component={Test} />
  5. // 初始时重定向到 about 组件
  6. <Redirect to="/about" />
  7. </Switch>
  1. 1. 路由连接, 用来改变指定到哪个路由
  2. 2. 备注: 当路由被选中时无法添加属性
  1. <Link to="/about">
  2. About
  3. </Link>
1. 路由连接, 用来改变指定到哪个路由 2. 备注: 当路由被选中时可以为路由指定添加类名, 如果不写 activeClassName=”” 默认添加类名为 active ```javascript About

// NavLink 的封装 MyNavLink 以后在使用 NavLink 时直接使用 MyNavLink 就可以省略代码 // 封装 import React, { Component } from ‘react’ import { NavLink } from ‘react-router-dom’ export default class MyNavLink extends Component { render() { console.log(this.props); // { “className”: “list-group-item”,”to”: “/about”,”children”: “About”} // “children”: “About” 可以获取标签题的内容携带在 children 身上, 所以可以展开运算符直接输出 return ( ) } } // 使用

About

  1. <Switch>
  2. 1. 当同一个路由注册不同的组件时, 匹配到第一个会继续向下匹配, 使用 Switch 则匹配到第一个不在向下匹配
  3. 2. 一般用于路由组件多的时候匹配到指定路由后, 退出匹配
  4. ```javascript
  5. <Switch>
  6. <Route path="/about" component={About} />
  7. <Route path="/home" component={Home} /> {/* 匹配到第一个 Home 则不会继续向下匹配 */}
  8. <Route path="/home" component={Test} />
  9. </Switch>

其它

  1. 1. history 对象
  2. 2. match 对象
  3. 3. withRouter 函数

2️⃣ 路由组件与一般组件的不同

写法不同:

  1. 1. 一般组件:<Demo/>
  2. 2. 路由组件:<Route path="/demo" component={Demo}/>

存放位置不同:

  1. 1. 一般组件:components
  2. 2. 路由组件:pages

接收到的props不同:

  1. 1. 一般组件:写组件标签时传递了什么,就能收到什么
  2. 2. 路由组件:接收到三个固定的属性
  3. 1. history:
  4. 1. go:ƒ go(n)
  5. 1. 指定前进或后退几步, 取决于 n 的数值
  6. 2. goBack:ƒ goBack()
  7. 1. 后退
  8. 3. goForward:ƒ goForward()
  9. 1. 前进
  10. 4. push:ƒ push(path, state)
  11. 1. push 跳转(path 指定路径, state指定state参数)
  12. 5. replace:ƒ replace(path, state)
  13. 1. replace 跳转(path 指定路径, state指定state参数)
  14. 2. location:
  15. 1. pathname"/about"
  16. 1. 获取当前路径
  17. 2. search""
  18. 1. sraech 参数
  19. 3. stateundefined
  20. 1. state 参数
  21. 3. match:
  22. 1. params:{}
  23. 1. params 参数
  24. 2. path"/about"
  25. 1. 获取当前路径
  26. 3. url"/about"
  27. 1. 获取当前路径

2️⃣ 路由的模糊匹配与精准匹配

严格匹配一般不推荐使用
在多级路由时开启严格匹配会出现无法匹配二级路由

  1. // 模糊匹配 - 默认为模糊匹配
  2. <MyNavLink to="/about/a/b">
  3. About
  4. </MyNavLink>
  5. <Route path="/about" component={About} /> // 可以匹配到
  6. <MyNavLink to="/about">
  7. About
  8. </MyNavLink>
  9. <Route path="/about/a/b" component={About} /> // 匹配不到
  10. <MyNavLink to="a/about/b"> // 开头不一样就匹配不到
  11. About
  12. </MyNavLink>
  13. <Route path="/about" component={About} /> // 匹配不到
  14. // 精准匹配 - 匹配的路由必须一模一样
  15. // 在代码中添加 exact 或 exact={true} 开启精准匹配
  16. <MyNavLink to="/about">
  17. About
  18. </MyNavLink>
  19. <Route exact path="/about" component={About} />

2️⃣ 嵌套路由的使用

注册子路由时要写上父路由的 path ( 路径 ) 值
路由的匹配是按照注册路由的顺序进行的

2️⃣ 向路由组件传递参数

params参数

  1. 1. 路由链接(携带参数):<Link to='/demo/test/tom/18'}>详情
  2. 2. 注册路由(声明接收):
  3. 3. 接收参数:this.props.match.params

search参数

  1. 1. 路由链接(携带参数):<Link to='/demo/test?name=tom&age=18'}>详情
  2. 2. 注册路由(无需声明,正常注册即可):
  3. 3. 接收参数:this.props.location.search
  4. 4. 备注:获取到的searchurlencoded编码字符串,需要借助querystring包解析
  5. 1. import qs from "querystring";

state参数

  1. 1. 路由链接(携带参数):<Link to={{pathname:'/demo/test',state:{name:'tom',age:18}}}>详情
  2. 2. 注册路由(无需声明,正常注册即可):
  3. 3. 接收参数:this.props.location.state
  4. 4. 备注:刷新也可以保留住参数

2️⃣ 编程式路由导航

借助this.prosp.history对象上的API对操作路由跳转、前进、后退

  1. 1. this.prosp.history.push()
  2. 2. this.prosp.history.replace()
  3. 3. this.prosp.history.goBack()
  4. 4. this.prosp.history.goForward()
  5. 5. this.prosp.history.go()

2️⃣ 一般组件使用路由组件 API

引入:import {withRouter} from ‘react-router-dom’
使用

  1. 1. export default withRouter(Header)
  2. 1. withRouter可以加工一般组件,让一般组件具备路由组件所特有的 API
  3. 2. withRouter的返回值是一个新组件

使用场景一般是在一个集合组件上操作路由的前进后退

2️⃣ BrowserRouter与HashRouter的区别

底层原理不一样:

  1. 1. BrowserRouter使用的是H5history API,不兼容IE9及以下版本。
  2. 2. HashRouter使用的是URL的哈希值。

path 表现形式不一样

  1. 1. BrowserRouter的路径中没有#,例如:localhost:3000/demo/test
  2. 2. HashRouter的路径包含#,例如:localhost:3000/#/demo/test

刷新后对路由 state 参数的影响

  1. 1. BrowserRouter没有任何影响,因为state保存在history对象中。
  2. 2. HashRouter刷新后会导致路由state参数的丢失!!!

备注:HashRouter可以用于解决一些路径错误相关的问题。