React基础知识总结(一)

137 阅读17分钟

三、JSX

设想如下变量声明:

const element = <h1>Hello, world!</h1>;

这个有趣的标签语法既不是字符串也不是 HTML。

它被称为 JSX,是一个 JavaScript 的语法扩展。

JSX 可以生成 React “元素”。

React 不强制要求使用 JSX,但是大多数人发现,在 JavaScript 代码中将 JSX 和 UI 放在一起时,会在视觉上有辅助作用。它还可以使 React 显示更多有用的错误和警告消息。

src文件夹下只保留index.js

3.1 jsx语法详解

在下面的例子中,我们声明了一个名为 name 的变量,然后在 JSX 中使用它,并将它包裹在大括号中

src/index.js

// src/index.js// // react V18版本前写法   后续会 移除不支持  ReactDOM.render 写法
// import React from 'react'; // 引入react的基础模块
// import ReactDOM from 'react-dom'; // 渲染DOM内容
// // jsx语法 - javascript xml
// const element = <h1>hello, react course!</h1>// // 找到 页面 中的 root 节点  public/index.html  <div id="root"></div>
// ReactDOM.render(element, document.getElementById('root'))
​
​
// react V18版本后写法
import React from 'react'; // 引入react的基础模块
import ReactDOM from 'react-dom/client'; // 渲染DOM内容
// jsx语法 - javascript xml
const element = <h1>hello, react course!</h1>// 找到 页面 中的 root 节点  public/index.html  <div id="root"></div>
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(element);
​

在 JSX 语法中,你可以在大括号内放置任何有效的 JavaScript 表达式。例如,2+2user.firstNameformatName(user) 都是有效的 JavaScript 表达式。

在下面的示例中,我们将调用 JavaScript 函数 formatName(user) 的结果,并将结果嵌入到 <div> 元素中。

src/index.js

// src/index.js// // react V18版本前写法   后续会 移除不支持  ReactDOM.render 写法
// import React from 'react'; // 引入react的基础模块
// import ReactDOM from 'react-dom'; // 渲染DOM内容
// // jsx语法 - javascript xml
// const element = <h1>hello, react course!</h1>// // 找到 页面 中的 root 节点  public/index.html  <div id="root"></div>
// ReactDOM.render(element, document.getElementById('root'))
​
​
// react V18版本后写法
import React from 'react'; // 引入react的基础模块
import ReactDOM from 'react-dom/client'; // 渲染DOM内容function formatName(name) {
  return name
}
const flag = true
// jsx语法 - javascript xml
// 不可以在jsx代码中直接使用循环控制语句
/**
 * {
 *    if (flag) {
 *      return 真
 *    }
 * }
 * {
 *   for (var i = 0; i < 10; i++) {}
 * }
 * 但是可以在循环控制语句中处理jsx 代码
 * 
 * if (flag ){
 *  return <div>{}</div>
 * }
 */
const element = <h1>hello, react course! { formatName('吴大勋') }  { 2 + 2 } { flag ? '真' : '假' }</h1>
​
​
// 找到 页面 中的 root 节点  public/index.html  <div id="root"></div>
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(element);
​

jsx也是一个表达式

在编译之后,JSX 表达式会被转为普通 JavaScript 函数调用,并且对其取值后得到 JavaScript 对象。

也就是说,你可以在 if 语句和 for 循环的代码块中使用 JSX,将 JSX 赋值给变量,把 JSX 当作参数传入,以及从函数中返回 JSX

因为 JSX 语法上更接近 JavaScript 而不是 HTML,所以 React DOM 使用 camelCase(小驼峰命名)来定义属性的名称,而不使用 HTML 属性名称的命名约定。

例如,JSX 里的 class 变成了 className,而 tabindex 则变为 tabIndex

<div class="test"></div>  ===> <div className="test"></div>
<label for="username">姓名</label><input id="username"/>  ===>
<label htmlFor="username">姓名</label><input id="username"/>

Babel 会把 JSX 转译成一个名为 React.createElement() 函数调用。

上述代码都没见到使用过 React 模块,但是显示却用了,为什么?

3.2 React.createElement

先看一个代码

src/index.js

// src/index.js
import React from 'react'import ReactDOM from 'react-dom/client'// jsx代码中写多行代码时,建议使用()包裹
// 标签样式 class样式,将其改为 className;因为class在js中是一个关键字
// jsx代码会导致 React 模块被调用,因为底层调用了 React.createElement,等同于vue中的渲染函数 h
const app = (
  <div className='container'>
    <div className='box'>
      <header className='header'>header</header>
      <div className='content'>content</div>
    </div>
    <footer className='footer'>footer</footer>
  </div>
)
​
​
const root = ReactDOM.createRoot(document.querySelector('#root'))
​
root.render(app)

React.createElement() 会预先执行一些检查,以帮助你编写无错代码,但实际上它创建了一个这样的对象:

// 注意:这是简化过的结构
const element = {
  type: 'div',
  props: {
    className: 'box',
    children: 'hello react'
  }
};

这些对象被称为 “React 元素”。它们描述了你希望在屏幕上看到的内容。React 通过读取这些对象,然后使用它们来构建 DOM 以及保持随时更新。

// src/index.js
import React from 'react'
import ReactDOM from 'react-dom/client'// jsx代码中写多行代码时,建议使用()包裹
// 标签样式 class样式,将其改为 className;因为class在js中是一个关键字
// jsx代码会导致 React 模块被调用,因为底层调用了 React.createElement,等同于vue中的渲染函数 h
// const element = (
//   <div className='container'>
//     <div className='box'>
//       <header className='header'>header</header>
//       <div className='content'>content</div>
//     </div>
//     <footer className='footer'>footer</footer>
//   </div>
// )// React.createElement 中渲染子元素时需要添加 唯一的key元素
const element = React.createElement('div', { className: 'container'}, [
  React.createElement('div', { className: 'box', key: '22'}, [
    React.createElement('header', { className: 'header', key: '0' }, 'header'),
    React.createElement('div', { className: 'content', key: '1' }, 'content')
  ]),
  React.createElement('footer', { className: 'footer', key: "33"}, 'footer')
])
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(element);
​

四、组件定义

组件允许你将 UI 拆分为独立可复用的代码片段,并对每个片段进行独立构思。

组件,从概念上类似于 JavaScript 函数。它接受任意的入参(即 “props”),并返回用于描述页面展示内容的 React 元素。

4.1 类组件

ES6的加入让JavaScript直接支持使用class来定义一个类,react的创建类组件的方式就是使用的类的继承,ES6 class是一种使用React组件的写法,它使用了ES6标准语法来构建

// src/index.js
import React from 'react'
import ReactDOM from 'react-dom/client'
​
// 类组件
// 首字母必须大写
class App extends React.Component {
  // es5 的继承机制  Vs es6的继承机制
  // ES6 规定,子类必须在constructor()方法中调用super(),否则就会报错。
  // 这是因为子类自己的this对象,必须先通过父类的构造函数完成塑造,
  // 得到与父类同样的实例属性和方法,然后再对其进行加工,添加子类自己的实例属性和方法。
  // 如果不调用super()方法,子类就得不到自己的this对象。
​
  // ES6 的继承机制,与 ES5 完全不同。
  // ES5 的继承机制,是先创造一个独立的子类的实例对象,
  // 然后再将父类的方法添加到这个对象上面,即“实例在前,继承在后”。
  // ES6 的继承机制,则是先将父类的属性和方法,加到一个空的对象上面,
  // 然后再将该对象作为子类的实例,即“继承在前,实例在后”。
  // 这就是为什么 ES6 的继承必须先调用super()方法,
  // 因为这一步会生成一个继承父类的this对象,没有这一步就无法继承父类。
  constructor (props) { // 如果只是调用super 方法,react识别为没有必要写这个钩子函数
    super(props) // 先将父类的属性和方法,加到一个空的对象上面
​
    this.state = { // 添加子类自己的实例属性和方法。 // react 类组件的初始化状态
      count: 10000
    }
  }
​
  render () { // render 函数是 类组件的生命周期钩子函数,一个类组件必不可少的一个函数
​
    // 需要返回jsx代码
    return (
      <div>hello world, { this.state.count }</div>
    )
  }
}
​
const root = ReactDOM.createRoot(document.getElementById('root'));
​
// js中的类在使用时 ,需要使用new关键字实例化
// React中组件直接可以拿来使用  ---  使用 标签形式
root.render(<App />);
​

4.2 函数组件

定义组件最简单的方式就是编写 JavaScript 函数

// src/index.js
import React from 'react'
import ReactDOM from 'react-dom/client'// 函数式组件 - 函数 - 组件 - 函数的名字首字母大写
// 组件 - 标签 - 小写当作固有的html标签// 如何定义一个函数// function App () {
//   return (
//     <div> hello 声明函数</div>
//   )
// }// const App = function () {
//   return (
//     <div> hello 表达式函数</div>
//   )
// }// const App = () => {
//   return (
//     <div>hello箭头函数</div>
//   )
// }const App  = () => (
  <div>hello 箭头函数简写形式</div>
)
​
const root = ReactDOM.createRoot(document.getElementById('root'));
​
// React中组件直接可以拿来使用  ---  使用 标签形式
root.render(<App />);
​

4.3 两组组件的区别

  • 组件的定义方式不同。
  • 生命周期不同:类组件有,函数式组件没有。
  • 副作用操作执行不同:class组件通过生命周期函数,函数组件用hook的useEffect。
  • state的定义、读取、修改方式不同:函数组件用hook的useState。
  • this: class组件有,函数式组件没有。
  • 实例: class组件有,函数式组件没有。
  • ref使用不同:类组件可以获取子组件实例,函数式组件不可以,因为函数式组件没有实例。

官方推荐使用函数式组件,以上不同点虽然现在不明白是啥意思,没有关系,会随着大家的学习印象加深。

五、Props

5.1 Props详解

props是正常是外部传入的,组件内部也可以通过一些方式来初始化的设置,属性不能被组件自己更改,但是你可以通过父组件主动重新渲染的方式来传入新的 props

React 非常灵活,但它也有一个严格的规则:

所有 React 组件都必须像纯函数一样保护它们的 props 不被更改。

纯函数:输入一定,输出一定确定

总的来说,在使用一个组件的时候,可以把参数放在标签的属性当中,所有的属性都会作为组件 props 对象的键值。

通过箭头函数创建的组件,需要通过函数的默认参数来接收props

通过创建的组件,需要通过 this.props来接收

组件可以在其输出中引用其他组件 - 类vue的插槽。

<van-swipe>
    <van-swipe-item></van-swipe-item>
</van-swipe>

这就可以让我们用同一组件来抽象出任意层次的细节。

按钮,表单,对话框,甚至整个屏幕的内容:在 React 应用程序中,这些通常都会以组件的形式表示。

5.2 父子组件通信

5.2.1 构建一个父子组件

src/index.js

// src/index.js
import React from 'react'
import ReactDOM from 'react-dom/client'import App from './01_props/01Parent_Child' // 父子组件const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<App />);
​

src/01_props/01Parent_Child.jsx

// src/01_props/01Parent_Child.jsx
import React from 'react'
​
function Child2 () {
  return (<div>子组件-函数式组件</div>)
}
​
class Child1 extends React.Component {
  render () {
    return (
      <div>子组件 - 类组件</div>
    )
  }
}
​
class App extends React.Component {
  render () {
    return (
      <div>
        <h1>父子组件</h1>
        <Child1 />
        <Child2 />
      </div>
    )
  }
}
​
export default App

回顾vue父子传值:

父组件调用子组件的地方,添加自定义的属性,属性的值就i是需要传递给子组件的数据

如果属性的值是变量,boolean类型,number类型,对象,数组,null,undefined,正则,则需要使用绑定属性

在子组件定义的地方,添加props选项,props选项有2中写法

第一种写法为数组,数组的元素为自定义的属性名,即可在模板中通过自定义的属性名获取父组件 的值

第二种写法为对象,该对象又有两种写法

第一种写法,key值为自定义的属性名,value值为数据类型,即可在模板中通过自定义的属性名获取父组件 的值

第二种写法,key值为自定义的属性名,value值为对象,对象下的

key如果为type,表示数据类型

key如果为default,表示的自定义属性的默认值,如果默认值是数组或者对象,写为函数返回即可

key如果是required,表示该自定义的属性是不是必须传递的

key 如果是validator,表示需要自定义一个函数验证该自定义属性

项目中一般都以对象形式说明数据类型为主

5.2.2 父组件给子组件传值

src/index.js

// src/index.js
import React from 'react'
import ReactDOM from 'react-dom/client'// import App from './01_props/01Parent_Child' // 父子组件
import App from './01_props/02Parent_Child_value' // 父组件给子组件传值const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<App />);
​

src/01_props/02Parent_Child_value.jsx

// src/01_props/02Parent_Child_value.jsx
import React from 'react'// 如果子组件是函数式组件,则函数需要添加默认的参数为props, props中包含了 所有父组件传递的数据
function Child2 (props) {
  return (
  <div>
    子组件-函数式组件
    <div>{ props.name }</div>
    <div>{ props.val }</div>
    <div>{ props.flag }</div>
    <div>{ props.num }</div>
    <div>{ props.obj.a }</div>
    <div>{ props.arr }</div>
  </div>)
}
​
// 如果子组件是类组件,则在需要的地方,可以通过 this.props 获取到所有的父组件传递的数据
class Child1 extends React.Component {
  render () {
    return (
      <div>
        子组件 - 类组件
        <div>{ this.props.name }</div>
        <div>{ this.props.val }</div>
        <div>{ this.props.flag }</div>
        <div>{ this.props.num }</div>
        <div>{ this.props.obj.a }</div>
        <div>{ this.props.arr }</div>
      </div>
    )
  }
}
​
class App extends React.Component {
  render () {
    const val1 = "10K"
    const val2 = "20K"
    return (
      <div>
        <h1>父子组件</h1>
        {
          /*  
            父组件调用子组件的地方,添加自定义属性,属性的值就是需要传递给子组件的值
            如果属性的值是变量、boolean类型、number类型、对象、数组、null、undefined,
            需要使用 {} 包裹
          */
        }
        <Child1 name="child1" val={ val1 } flag={ true }  num={ 1 } obj={ { a: 10 } } arr = { [1, 2, 3] }/>
        <Child2 name="child2" val={ val2 } flag={ false } num={ 2 } obj={ { a: 100 } } arr = { [4, 5, 6] }/>
      </div>
    )
  }
}
​
export default App

5.2.3 父组件给子组件传值设置默认值

src/index.js

// src/index.js
import React from 'react'
import ReactDOM from 'react-dom/client'// import App from './01_props/01Parent_Child' // 父子组件
// import App from './01_props/02Parent_Child_value' // 父组件给子组件传值
import App from './01_props/03Parent_Child_default' // 父组件给子组件传值 并且设置默认值const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<App />);
​

src/01_props/03_Parent_Chil_default.jsx

// src/01_props/03Parent_Child_default.jsx
import React from 'react'
// 如果需要给传递的属性设置默认值,
//    不管是函数式组件,还是类组件,都可以通过定义完组件后 通过 组件.defaultProps = {} 设置默认值
//    如果是类组件,还可以通过类的静态属性设置默认值  static defualtProps = {} 设置默认值
​
​
// 如果子组件是函数式组件,则函数需要添加默认的参数为props, props中包含了 所有父组件传递的数据
function Child2 (props) {
  return (
  <div>
    子组件-函数式组件
    <div>{ props.name }</div>
    <div>{ props.val }</div>
    <div>{ props.flag }</div>
    <div>{ props.num }</div>
    <div>{ props.obj.a }</div>
    <div>{ props.arr }</div>
  </div>)
}
Child2.defaultProps = {
  name: 'child2组件'
}
​
// 如果子组件是类组件,则在需要的地方,可以通过 this.props 获取到所有的父组件传递的数据
class Child1 extends React.Component {
​
  static defaultProps = {
    name: 'child111组件'
  }
​
  render () {
    return (
      <div>
        子组件 - 类组件
        <div>{ this.props.name }</div>
        <div>{ this.props.val }</div>
        <div>{ this.props.flag }</div>
        <div>{ this.props.num }</div>
        <div>{ this.props.obj.a }</div>
        <div>{ this.props.arr }</div>
      </div>
    )
  }
}
// Child1.defaultProps = {
//   name: 'child1组件'
// }
class App extends React.Component {
  render () {
    const val1 = "10K"
    const val2 = "20K"
    return (
      <div>
        <h1>父子组件</h1>
        {
          /*  
            父组件调用子组件的地方,添加自定义属性,属性的值就是需要传递给子组件的值
            如果属性的值是变量、boolean类型、number类型、对象、数组、null、undefined,
            需要使用 {} 包裹
          */
        }
        <Child1 name="child1" val={ val1 } flag={ true }  num={ 1 } obj={ { a: 10 } } arr = { [1, 2, 3] }/>
        <hr />
        <Child2 name="child2" val={ val2 } flag={ false } num={ 2 } obj={ { a: 100 } } arr = { [4, 5, 6] }/>
        <hr />
        <Child1 val={ val1 } flag={ true }  num={ 1 } obj={ { a: 10 } } arr = { [1, 2, 3] }/>
      </div>
    )
  }
}
​
export default App

5.2.4 使用prop-types属性验证

自 React v15.5 起,React.PropTypes 已移入另一个包中。请使用 prop-types 代替。

$ cnpm i prop-types -D
import PropTypes from 'prop-types';
​
MyComponent.propTypes = {
// 你可以将属性声明为 JS 原生类型,默认情况下
// 这些属性都是可选的。
optionalArray: PropTypes.array,
optionalBool: PropTypes.bool,
optionalFunc: PropTypes.func,
optionalNumber: PropTypes.number,
optionalObject: PropTypes.object,
optionalString: PropTypes.string,
optionalSymbol: PropTypes.symbol,
​
// 任何可被渲染的元素(包括数字、字符串、元素或数组)
// (或 Fragment) 也包含这些类型。
optionalNode: PropTypes.node,
​
// 一个 React 元素。
optionalElement: PropTypes.element,
​
// 一个 React 元素类型(即,MyComponent)。
optionalElementType: PropTypes.elementType,
​
// 你也可以声明 prop 为类的实例,这里使用
// JS 的 instanceof 操作符。
optionalMessage: PropTypes.instanceOf(Message),
​
// 你可以让你的 prop 只能是特定的值,指定它为
// 枚举类型。
optionalEnum: PropTypes.oneOf(['News', 'Photos']),
​
// 一个对象可以是几种类型中的任意一个类型
optionalUnion: PropTypes.oneOfType([
PropTypes.string,
PropTypes.number,
PropTypes.instanceOf(Message)
]),
​
// 可以指定一个数组由某一类型的元素组成
optionalArrayOf: PropTypes.arrayOf(PropTypes.number),
​
// 可以指定一个对象由某一类型的值组成
optionalObjectOf: PropTypes.objectOf(PropTypes.number),
​
// 可以指定一个对象由特定的类型值组成
optionalObjectWithShape: PropTypes.shape({
color: PropTypes.string,
fontSize: PropTypes.number
}),
​
// An object with warnings on extra properties
optionalObjectWithStrictShape: PropTypes.exact({
name: PropTypes.string,
quantity: PropTypes.number
}),
​
// 你可以在任何 PropTypes 属性后面加上 `isRequired` ,确保
// 这个 prop 没有被提供时,会打印警告信息。
requiredFunc: PropTypes.func.isRequired,
​
// 任意类型的必需数据
requiredAny: PropTypes.any.isRequired,
​
// 你可以指定一个自定义验证器。它在验证失败时应返回一个 Error 对象。
// 请不要使用 `console.warn` 或抛出异常,因为这在 `oneOfType` 中不会起作用。
customProp: function(props, propName, componentName) {
if (!/matchme/.test(props[propName])) {
return new Error(
  'Invalid prop `' + propName + '` supplied to' +
  ' `' + componentName + '`. Validation failed.'
);
}
},
​
// 你也可以提供一个自定义的 `arrayOf` 或 `objectOf` 验证器。
// 它应该在验证失败时返回一个 Error 对象。
// 验证器将验证数组或对象中的每个值。验证器的前两个参数
// 第一个是数组或对象本身
// 第二个是他们当前的键。
customArrayProp: PropTypes.arrayOf(function(propValue, key, componentName, location, propFullName) {
if (!/matchme/.test(propValue[key])) {
return new Error(
  'Invalid prop `' + propFullName + '` supplied to' +
  ' `' + componentName + '`. Validation failed.'
);
}
})
};
​
​

src/index.js

// src/index.js
import React from 'react'
import ReactDOM from 'react-dom/client'// import App from './01_props/01Parent_Child' // 父子组件
// import App from './01_props/02Parent_Child_value' // 父组件给子组件传值
// import App from './01_props/03Parent_Child_default' // 父组件给子组件传值 并且设置默认值
import App from './01_props/04Parent_Child_type' // 父组件给子组件传值 并且设置默认值,验证数据类型const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<App />);
​

src/01_props/04_Parent_Chil_type.jsx

// src/01_props/04_Parent_Chil_type.jsx
import React from 'react'
// 如果需要给传递的属性设置默认值,
//    不管是函数式组件,还是类组件,都可以通过定义完组件后 通过 组件.defaultProps = {} 设置默认值
//    如果是类组件,还可以通过类的静态属性设置默认值  static defualtProps = {} 设置默认值
// 如果需要对父组件传递的值进行 属性的属性数据类型的验证,则需要 通过 第三方模块 prop-types 搞定
//    通过 组件.propTypes = { key: PropTypes.数据类型 }
//    如果属性的值是必须传递的 通过 组件.propTypes = { key: PropTypes.数据类型.isRequired }
//    如果遇到属性的值需要满足 特定的需求,可以自定义验证规则 ,必须返回 new Error 对象
import PropTypes from 'prop-types'
​
​
// 如果子组件是函数式组件,则函数需要添加默认的参数为props, props中包含了 所有父组件传递的数据
function Child2 (props) {
  return (
  <div>
    子组件-函数式组件
    <div>{ props.name }</div>
    <div>{ props.val }</div>
    <div>{ props.flag }</div>
    <div>{ props.num }</div>
    <div>{ props.obj.a }</div>
    <div>{ props.arr }</div>
  </div>)
}
Child2.defaultProps = {
  name: 'child2组件'
}
Child2.propTypes = {
  name: PropTypes.string,
  // val: PropTypes.string,
  val: PropTypes.oneOfType([ // 既可以是string类型,还可以是 number类型,但是必须传递数据
    PropTypes.string, PropTypes.number
  ]).isRequired,
  flag: PropTypes.bool,
  num: PropTypes.number,
  // obj: PropTypes.shape({ a: PropTypes.string }),
  obj: PropTypes.shape({ a: PropTypes.oneOfType([ // 设置了对象的key为a,value值 string 或者number 类型
    PropTypes.string, PropTypes.number
  ]) }),
  // arr: PropTypes.array
  arr: PropTypes.arrayOf(PropTypes.number)
}
​
// 如果子组件是类组件,则在需要的地方,可以通过 this.props 获取到所有的父组件传递的数据
class Child1 extends React.Component {
​
  static defaultProps = {
    name: 'child111组件'
  }
​
  render () {
    return (
      <div>
        子组件 - 类组件
        <div>{ this.props.name }</div>
        <div>{ this.props.val }</div>
        <div>{ this.props.flag }</div>
        <div>{ this.props.num }</div>
        <div>{ this.props.obj.a }</div>
        <div>{ this.props.arr }</div>
      </div>
    )
  }
}
​
Child1.propTypes = {
  arr (props, propName, componentName) {
    if (props[propName].length < 4) {
      return new Error(`${propName}的属性值的长度不能小于4`)
    }
  }
}
// Child1.defaultProps = {
//   name: 'child1组件'
// }
class App extends React.Component {
  render () {
    const val1 = "10K"
    const val2 = "20K"
    return (
      <div>
        <h1>父子组件</h1>
        {
          /*  
            父组件调用子组件的地方,添加自定义属性,属性的值就是需要传递给子组件的值
            如果属性的值是变量、boolean类型、number类型、对象、数组、null、undefined,
            需要使用 {} 包裹
          */
        }
        <Child1 name="child1" val={ val1 } flag={ true }  num={ 1 } obj={ { a: 10 } } arr = { [1, 2, 3] }/>
        <hr />
        <Child2 name="child2" val={ val2 } flag={ false } num={ 2 } obj={ { a: 100 } } arr = { [4, 5, 6] }/>
        <hr />
        <Child1 val={ val1 } flag={ true }  num={ 1 } obj={ { a: 10 } } arr = { [1, 2, 3] }/>
      </div>
    )
  }
}
​
export default App

5.3 props.children

我们知道使用组件的时候,可以嵌套。要在自定义组件中使用嵌套结构,就需要使用 props.children

等同于 vue中的 slot 插槽

内容分发 == 插槽

自定义组件内部的内容显示还是不显示(看组件定义时有没有slot标签),在哪里显示(定义组件模板时slot的位置),如何显示(具名插槽),这就是内容分发所干的活

src/index.js

// src/index.js
import React from 'react'
import ReactDOM from 'react-dom/client'// import App from './01_props/01Parent_Child' // 父子组件
// import App from './01_props/02Parent_Child_value' // 父组件给子组件传值
// import App from './01_props/03Parent_Child_default' // 父组件给子组件传值 并且设置默认值
// import App from './01_props/04Parent_Child_type' // 父组件给子组件传值 并且设置默认值,验证数据类型
import App from './01_props/05App_props_children' // 类插槽 props.childrenconst root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<App />);
​

src/01_props/05App_props_children.jsx

// src/01_props/05App_props_children.jsx
import React from 'react'
// vue <slot></slot>
// react props.children
function Child2 (props) {
  return (<div>child2组件 - { props.children } </div>)
}
​
class Child1 extends React.Component {
  render () {
    return (
      <div>child1组件 - { this.props.children }</div>
    )
  }
}
​
class App extends React.Component {
  render () {
    return (
      <div>
        <h1>类插槽</h1>
        <Child1>
          <h1>111111</h1>
        </Child1>
        <Child2>
          <h1>222222</h1>
        </Child2>
      </div>
    )
  }
}
​
export default App

如果需要给组件添加多个元素,并且显示在多个位置,可以如下设置:

src/index.js

// src/index.js
import React from 'react'
import ReactDOM from 'react-dom/client'// import App from './01_props/01Parent_Child' // 父子组件
// import App from './01_props/02Parent_Child_value' // 父组件给子组件传值
// import App from './01_props/03Parent_Child_default' // 父组件给子组件传值 并且设置默认值
// import App from './01_props/04Parent_Child_type' // 父组件给子组件传值 并且设置默认值,验证数据类型
// import App from './01_props/05App_props_children' // 类插槽 props.children
import App from './01_props/06App_multiple_props_children' // 类具名插槽 props.childrenconst root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<App />);
​

src/01_props/06_App_multiple_props_children.jsx

// src/01_props/06App_multiple_props_children.jsximport React from 'react';
// vue <slot name="header"></slot>  <template #header></template>
// 下标与需要的效果以及传递进来的书写顺序有关
const MyApp = (props) => {
  console.log("props", props)
  return (
    <div className='container'>
      { props.children[2] }
      { props.children[0] }
      { props.children[1] }
    </div>
  )
}
​
const App = () => {
  return (
    <div>
      <MyApp>
        <div className="content">home content</div>
        <footer className="footer">home footer</footer>
        <header className="header">home header</header>
      </MyApp>
      <hr/>
      <MyApp>
        <div className="content">kind content</div>
        <footer className="footer">kind footer</footer>
        <header className="header">kind header</header>
      </MyApp>
    </div>
  );
};
​
export default App;

实现类似vue的具名插槽,需要通过 props.children 的下标去访问

5.4 render props特性

使用 Render Props 来解决横切关注点(Cross-Cutting Concerns)

什么情况下需要使用状态管理器

  • 多个视图需要依赖于同一个状态
  • 来自不同视图的行为需要变更同一个状态

组件是 React 代码复用的主要单元,但如何将一个组件封装的状态或行为 共享给其他需要相同状态的组件 并不总是显而易见。

以下组件跟踪 Web 应用程序中的鼠标位置:

src/index.js

// src/index.js
import React from 'react'
import ReactDOM from 'react-dom/client'// import App from './01_props/01Parent_Child' // 父子组件
// import App from './01_props/02Parent_Child_value' // 父组件给子组件传值
// import App from './01_props/03Parent_Child_default' // 父组件给子组件传值 并且设置默认值
// import App from './01_props/04Parent_Child_type' // 父组件给子组件传值 并且设置默认值,验证数据类型
// import App from './01_props/05App_props_children' // 类插槽 props.children
// import App from './01_props/06App_multiple_props_children' // 类具名插槽 props.children
import App from './01_props/07App_mouse_tracker' // 鼠标跟随const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<App />);
​

src/01_props/07App_mouse_tracker.jsx

还没有学习状态state以及事件处理,这里先用

// src/01_props/07App_mouse_tracker.jsx
import React from 'react';
​
class App extends React.Component {
  constructor(props) {
    super(props)
    this.state = {
      x: 0, y: 0
    }
  }
  render () {
    return (
      <div 
        style = { {
          width: '100vw',
          height: '100vh',
          backgroundColor: "skyblue"
        } }
        onMouseMove={ (event) => { // react 事件 小驼峰 ---- 保证 是一个函数,同时需要保证this指向
          // 修改状态
          this.setState({
            x: event.clientX,
            y: event.clientY
          })
        }}
      >
        鼠标的位置为: (x: { this.state.x }, y: { this.state.y })
      </div>
    )
  }
}
export default App;

当光标在屏幕上移动时,组件在 <div> 中显示其坐标。

现在的问题是:我们如何在另一个组件中复用这个行为?换个说法,若另一个组件需要知道鼠标位置,我们能否封装这一行为,以便轻松地与其他组件共享它?

render prop 是一个用于告知组件需要渲染什么内容的函数 prop。

src/index.js

// src/index.js
import React from 'react'
import ReactDOM from 'react-dom/client'// import App from './01_props/01Parent_Child' // 父子组件
// import App from './01_props/02Parent_Child_value' // 父组件给子组件传值
// import App from './01_props/03Parent_Child_default' // 父组件给子组件传值 并且设置默认值
// import App from './01_props/04Parent_Child_type' // 父组件给子组件传值 并且设置默认值,验证数据类型
// import App from './01_props/05App_props_children' // 类插槽 props.children
// import App from './01_props/06App_multiple_props_children' // 类具名插槽 props.children
// import App from './01_props/07App_mouse_tracker' // 鼠标跟随
import App from './01_props/08_App_render_props' // 渲染属性 render propsconst root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<App />);
​

src/01_props/08App_render_props.jsx

// src/01_props/08_App_render_props.jsx
import React from 'react'
// 渲染属性共享组件的状态
// 在调用需要共享的组件(Mouse)上,添加一个render的自定义属性,该属性是一个自定义函数
// 在自定义函数的内部返回需要共享给的那个组件(Cat)
// 在需要共享的组件(Mouse)内部,通过 this.props.render() 或者 props.render() 即可调用,参数即为需要共享的状态
// 那么在定义自定义render属性的函数内部,就会接收到 参数,通过返回的组件(Cat)传递该参数即可
​
const Cat = (props) => {
  return (
    <div>
      cat 鼠标的位置为: (x: { props.points.x }, y: {  props.points.y })
    </div>
  )
}
​
const Dog = (props) => {
  return (
    <div>
      Dog 鼠标的位置为: (x: { props.points.x }, y: {  props.points.y })
    </div>
  )
}
​
class Mouse extends React.Component {
  constructor(props) {
    super(props)
    this.state = {
      x: 0, y: 0
    }
  }
  render () {
    return (
      <div 
        style = { {
          width: '100vw',
          height: '50vh',
          backgroundColor: "skyblue"
        } }
        onMouseMove={ (event) => { // react 事件 小驼峰 ---- 保证 是一个函数,同时需要保证this指向
          // 修改状态
          this.setState({
            x: event.clientX,
            y: event.clientY
          })
        }}
      >
        {
          this.props.render({
            x: this.state.x,
            y: this.state.y
          })
        }
      </div>
    )
  }
}
​
class App extends React.Component {
  render () {
    return (
      <div> 
        <h1>render props</h1>
        <Mouse render = { (points) => {
          return <Cat points = { points }/>
        } }></Mouse>
        <Mouse render = { (points) => {
          return <Dog points = { points }/>
        } }></Mouse>
      </div>
    )
  }
}
​
export default App
​
​
// function test (points) {}// test({x: 1, y: 1})// <Child test = { (points) => {
//   return <Cat points = { points }/>
// }} />
​
​
// <Child />// props.test({x: 1, y: 1})

此案例实际上完成了react中子组件给父组件传值