三、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+2,user.firstName或formatName(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 属性名称的命名约定。
<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 -Dimport 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.children
const 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.children
const 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.jsx
import 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 props
const 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中子组件给父组件传值