焦铭才 發表於 2022-3-13 23:39:00

七天接手react项目 系列 —— react 起步

<blockquote>
<p>其他章节请看:</p>
<p>七天接手react项目 系列</p>
</blockquote>
<h2 id="react-起步">react 起步</h2>
<h3 id="背景">背景</h3>
<p>假如七天后必须接手一个 react 项目(spug - 一个开源运维平台),而笔者只会 vue,之前没有接触过 react,此刻能做的就是立刻展开一个“<strong>7天 react 扫盲活动</strong>”。</p>
<h3 id="react-活动扫盲方针">react 活动扫盲方针</h3>
<ol>
<li>以读懂 spug 项目为目标</li>
<li>无需对每个知识点深究</li>
<li>功能优先能实现,代码质量无需太苛刻</li>
</ol>
<h3 id="项目准备">项目准备</h3>
<p>将 spug 克隆到本地:</p>
<pre><code class="language-javascript">exercise&gt; git clone https://github.com/openspug/spug spug-dev-demo
Cloning into 'spug-dev-demo'...
fatal: unable to access 'https://github.com/openspug/spug/': OpenSSL SSL_read: Connection was reset, errno 10054
</code></pre>
<p>克隆失败,HTTPS 模式换成 SSH 再次下载:</p>
<pre><code class="language-javascript">exercise&gt; git clone git@github.com:openspug/spug.git spug-dev-demo
Cloning into 'spug-dev-demo'...
remote: Enumerating objects: 11675, done.
remote: Counting objects: 100% (4184/4184), done.
remote: Compressing objects: 100% (1161/1161), done.
remote: Total 11675 (delta 3157), reused 3939 (delta 2991), pack-reused 7491
Receiving objects: 100% (11675/11675), 5.09 MiB | 2.32 MiB/s, done.
Resolving deltas: 100% (8460/8460), done.
</code></pre>
<p>目录结构如下:</p>
<pre><code class="language-javascript">exercise\spug-dev-demo&gt; dir

Mode               LastWriteTime         Length Name
----               -------------         ------ ----
d-----         2022/3/12      9:52                .github
d-----         2022/3/12      9:52                docs
d-----         2022/3/12      9:52                spug_api
d-----         2022/3/12      9:52                spug_web
-a----         2022/3/12      9:52            9 .gitignore
-a----         2022/3/12      9:52          35184 LICENSE
-a----         2022/3/12      9:52         3732 README.md
</code></pre>
<p>我们前端主要关注 <code>spug_web</code> 这个项目。首先安装依赖包:</p>
<pre><code class="language-javascript">spug_web&gt; cnpm i
/ Installing @babel/plugin-transform-function-name@^7.8.3platform unsupported react-scripts@3.4.3 › babel-jest@24.9.0 › @jest/transform@24.9.0 › jest-haste-map@24.9.0 › fsevents@^1.2.7 Package require os(darwin) not compatible with your platform(win32)
- Installing @babel/plugin-transform-classes@^7.16.7 optional install error: Package require os(darwin) not compatible with your platform(win32)
....
</code></pre>
<p>本地启动项目:</p>
<pre><code class="language-javascript">spug_web&gt; npm run start

&gt; spug_web@3.0.0 start   
&gt; react-app-rewired start
</code></pre>
<p><img src="https://images.cnblogs.com/cnblogs_com/blogs/665957/galleries/2104361/o_220312023035_spug1.png"></p>
<p><em>注</em>:由于没有后端 api 的支持,所以不能登录进去。但至少可以从代码上分析这个前端项目。</p>
<h3 id="hello-world">hello-world</h3>
<p>直接使用 script 的方式引入 react:</p>
<pre><code class="language-javascript">// 新建 hello-world.html
&lt;body&gt;
    &lt;div id="root"&gt;
      &lt;!-- 此元素的内容将替换为您的组件 --&gt;
    &lt;/div&gt;

    &lt;!-- react 库--&gt;
    &lt;script src="https://unpkg.com/react@17/umd/react.development.js"&gt;&lt;/script&gt;
    &lt;!-- 用于处理 Dom 的 react 包 --&gt;
    &lt;script src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"&gt;&lt;/script&gt;
    &lt;!-- Babel 能够转换 JSX 语法 --&gt;
    &lt;script src="https://unpkg.com/@babel/standalone/babel.min.js"&gt;&lt;/script&gt;

    &lt;script type="text/babel"&gt;
      ReactDOM.render(
            // 注:无需添加字符串
            &lt;h1&gt;Hello, world!&lt;/h1&gt;,
            document.getElementById('root')
      );
    &lt;/script&gt;
&lt;/body&gt;
</code></pre>
<p>访问页面,浏览器显示“Hello, world!”</p>
<p>这里我们引入了三个库,分别是 react 核心库、处理 dom 的 react以及用于转换 jsx。</p>
<p><em>Tip</em>:</p>
<ul>
<li>笔者在 vscode 中安装 “open in browser” 插件,直接右键选择 “Open with Live Server” 即可。</li>
<li>此示例来自 react 官网 hello-world</li>
<li>React 和 ReactDOM 的 cdn 来自 react 官网-CDN 链接</li>
<li>unpkg 是一个快速的全球内容交付网络,适用于 npm 上的所有内容。 使用它可以快速轻松地从任何包中加载任何文件</li>
<li>react 和 vue 都是 javascript 库,都能用于构建用户界面
<ul>
<li>React 用于构建用户界面的 JavaScript 库 —— 官网</li>
<li>渐进式 JavaScript 框架 —— Vue 官网</li>
</ul>
</li>
</ul>
<h3 id="babel">babel</h3>
<p>Babel 是一个 JavaScript 编译器 —— 官网</p>
<p>babel 之前叫 6to5。意把 es6 转为 es5,后来目标变成支持 ECMAScript 所有语法,后来还支持将 JSX 转成 js。2015年2月,改名为 Bable。</p>
<p><em>Tip</em>:6to5 is now Babel —— not-born-to-die</p>
<p>使用 Babel 最容易上手的是直接在 html 页面中通过 cdn 引入它。就像这样:</p>
<pre><code class="language-javascript">&lt;body&gt;
    &lt;div id="output"&gt;&lt;/div&gt;
    &lt;!-- Load Babel --&gt;
    &lt;script src="https://unpkg.com/@babel/standalone/babel.min.js"&gt;&lt;/script&gt;
    &lt;!-- Your custom script here --&gt;
    &lt;script type="text/babel"&gt;
      const getMessage = () =&gt; "Hello World";
      document.getElementById("output").innerHTML = getMessage();
    &lt;/script&gt;
&lt;/body&gt;
</code></pre>
<p>浏览器页面显示:“Hello World”。</p>
<p>当在浏览器中加载时,@babel/standalone 将自动编译并执行所有类型为 text/babel 或 text/jsx 的脚本标签。</p>
<p><em>Tip</em>:@babel/standalone 提供了一个独立的 Babel 构建,用于浏览器和其他非 Node.js 环境</p>
<h3 id="为什么使用-jsx">为什么使用 JSX</h3>
<p>我们<strong>建议</strong>在 React 中配合使用 JSX,JSX 可以很好地描述 UI 应该呈现出它应有交互的本质形式 —— react 官网-JSX 简介</p>
<p>JSX 仅仅只是 <code>React.createElement(component, props, ...children)</code> 函数的语法糖 —— react 官网-深入 JSX</p>
<p><strong>语法糖</strong>,通常用起来更方便,功能或许也更强大。比如类(Class)只是我们自定义类的一个语法糖。jsx 是 <code>React.createElement</code> 的语法糖,意义应该也差不多。</p>
<p>在 hello-world 中我们使用的是语法糖(jsx)。就像这样:</p>
<pre><code class="language-javascript">// jsx
const reactElem = &lt;h1&gt;Hello, world!&lt;/h1&gt;
ReactDOM.render(
    reactElem,
    document.getElementById('root')
);
</code></pre>
<p>若不使用语法糖(使用 React.createElement)。就像这样:</p>
<pre><code class="language-javascript">const reactElem = React.createElement(
    'h1',
    {/* className: 'greeting' */ },
    'Hello, world!'
);
ReactDOM.render(reactElem, ...)
</code></pre>
<p>对比发现,jsx 更简洁。</p>
<h3 id="react-元素">React 元素</h3>
<p>react 元素是创建起来开销极小的普通对象,用于描述你在屏幕上想看到的内容。与浏览器的 dom 元素不同。</p>
<p>react 元素是构成 React 应用的最小砖块 —— react 官网-元素渲染</p>
<p><em>注</em>:不要混淆元素与组件,react 组件是由 react 元素构成的。</p>
<p>React.createElement 创建并返回指定类型的新的 react 元素。下面我们将 react 元素打印出来:</p>
<pre><code class="language-javascript">const reactElem = React.createElement(
    'h1',
    {},
    'Hello, world!'
)

console.log('reactElem: ', reactElem)
</code></pre>
<p>react 元素:</p>
<pre><code class="language-javascript">reactElem: {$$typeof: Symbol(react.element), type: 'h1', key: null, ref: null, props: {…}, …}
            $$typeof: Symbol(react.element)
            key: null
            props: {children: 'Hello, world!'}
            ref: null
            type: "h1"
            _owner: null
            _store: {validated: false}
            _self: null
            _source: null
            []: Object
</code></pre>
<p><em>Tip</em>:使用 jsx,输出也是一样的:</p>
<pre><code class="language-javascript">    const reactElem = &lt;h1&gt;Hello, world!&lt;/h1&gt;
    console.log('reactElem: ', reactElem)
</code></pre>
<p>react 元素只有极少的几个属性。而真实 dom 元素上的属性却要多得多。你可以通过浏览器运行以下代码,将鼠标移到 <code>root</code> 和 <code>reactElem</code> 对比查看属性数量。</p>
<pre><code class="language-javascript">const reactElem = &lt;h1&gt;Hello, world!&lt;/h1&gt;
let root = document.getElementById('root')
// 打个断点
debugger
</code></pre>
<p>react 元素是<strong>不可变对象</strong>。一旦被创建,你就无法更改它的子元素或者属性。</p>
<h4 id="react-只更新它需要更新的部分">React 只更新它需要更新的部分</h4>
<p>React DOM 会将元素和它的子元素与它们之前的状态进行比较,并只会进行必要的更新来使 DOM 达到预期的状态。</p>
<h3 id="jsx-语法规则">JSX 语法规则</h3>
<p>JSX,是一个 JavaScript 的语法扩展。JSX 可能会使人联想到模板语言,但它具有 JavaScript 的全部功能。</p>
<h4 id="不要写引号">不要写引号</h4>
<p>JSX 标签语法既不是字符串也不是 HTML。</p>
<pre><code class="language-javascript">// 正确。页面显示“Hello, world!”
const reactElem = &lt;h1&gt;Hello, world!&lt;/h1&gt;
</code></pre>
<pre><code class="language-javascript">// 错误。页面显示“&lt;h1&gt;Hello, world!&lt;/h1&gt;”
const reactElem = '&lt;h1&gt;Hello, world!&lt;/h1&gt;'
</code></pre>
<h4 id="引入-js-使用-">引入 js 使用 {}</h4>
<pre><code class="language-javascript">let _class = 'greeting'
let cnt = 'Hello, world!'
const reactElem = (
    &lt;h1 className={_class}&gt;
      {cnt.toLocaleUpperCase()}
    &lt;/h1&gt;
)
</code></pre>
<p>浏览器显示:”HELLO, WORLD!“。元素内容:</p>
<pre><code class="language-javascript">&lt;h1 class="greeting"&gt;HELLO, WORLD!&lt;/h1&gt;
</code></pre>
<h4 id="样式类名请使用-classname">样式类名请使用 className</h4>
<p>倘若是用 class。就像这样:</p>
<pre><code class="language-javascript">const reactElem = (
    &lt;h1 class={_class}&gt;
      {cnt.toLocaleUpperCase()}
    &lt;/h1&gt;
)
</code></pre>
<p>元素内容正常:</p>
<pre><code class="language-javascript">&lt;h1 class="greeting"&gt;HELLO, WORLD!&lt;/h1&gt;
</code></pre>
<p>但浏览器控制台报错:<code>Warning: Invalid DOM property 'class'. Did you mean 'className'?</code></p>
<h4 id="内联样式请使用-style-color-pink-">内联样式请使用 style={{ color: 'pink' }}</h4>
<p>倘若使用 <code>style="color:pink"</code>:</p>
<pre><code class="language-javascript">const reactElem = (
    &lt;h1 className={_class} style="color:pink"&gt;
      {cnt.toLocaleUpperCase()}
    &lt;/h1&gt;
   
)
</code></pre>
<p>控制台报错:</p>
<pre><code class="language-javascript">react-dom.development.js:2716 Uncaught Error: The 'style' prop expects a mapping from style properties to values, not a string. For example, style={{marginRight: spacing + 'em'}} when using JSX.

react-dom.development.js:2716 未捕获的错误:“样式”道具需要从样式属性到值的映射,而不是字符串。 例如,使用 JSX 时 style={{marginRight: spacing + 'em'}}。
</code></pre>
<p>改成 <code>{{ color: 'pink' }}</code> 则正常:</p>
<pre><code class="language-javascript">const reactElem = (
    &lt;h1 className={_class} style={{ color: 'pink' }}&gt;
      {cnt.toLocaleUpperCase()}
    &lt;/h1&gt;
)
</code></pre>
<h4 id="只能有一个根标签">只能有一个根标签</h4>
<pre><code class="language-javascript">const reactElem = (
    &lt;h1 className={_class} style={{ color: 'pink' }}&gt;
      {cnt.toLocaleUpperCase()}
    &lt;/h1&gt;
    &lt;p&gt;apple&lt;/p&gt;
)
</code></pre>
<p>vscode 红波浪线提示:<code>JSX expressions must have one parent element</code>(JSX 表达式必须有一个父元素)</p>
<p>包裹一个 div 即可。就像这样:</p>
<pre><code class="language-javascript">const reactElem = (
    &lt;div&gt;
      &lt;h1 className={_class} style={{ color: 'pink' }}&gt;
            {cnt.toLocaleUpperCase()}
      &lt;/h1&gt;
      &lt;p&gt;apple&lt;/p&gt;
    &lt;/div&gt;
)
</code></pre>
<h4 id="不要忘记闭合标签">不要忘记闭合标签</h4>
<pre><code class="language-javascript">&lt;div&gt;
    &lt;h1 className={_class} style={{ color: 'pink' }}&gt;
      {cnt.toLocaleUpperCase()}
    &lt;/h1&gt;
    /* input 标签未闭合 */
    &lt;input type="text"&gt;
&lt;/div&gt;
</code></pre>
<p>vscode 红波浪线提示:<code>JSX element 'input' has no corresponding closing tag</code>(JSX 元素“输入”没有相应的结束标记)。</p>
<p>浏览器运行控制台报错:Uncaught SyntaxError: /Inline Babel script: Unterminated JSX contents(未终止的 JSX 内容)。</p>
<p>以下两种闭合方式都可以:</p>
<pre><code class="language-javascript">&lt;input type="text" /&gt;

&lt;input type="text"&gt;&lt;/input&gt;
</code></pre>
<h4 id="自定义组件使用大写字母开头">自定义组件使用大写字母开头</h4>
<pre><code class="language-javascript">const reactElem = (
    &lt;div&gt;
      &lt;mybutton&gt;18&lt;/mybutton&gt;
    &lt;/div&gt;
)
</code></pre>
<p>页面显示:“18”。浏览器报错:</p>
<pre><code class="language-javascript">react-dom.development.js:61 Warning: The tag &lt;mybutton&gt; is unrecognized in this browser. If you meant to render a React component, start its name with an uppercase letter.

react-dom.development.js:61 警告:标签 &lt;mybutton&gt; 在此浏览器中无法识别。 如果您打算渲染一个 React 组件,请以大写字母开头。
</code></pre>
<p>以小写字母开头的元素代表一个 HTML 内置组件,比如 <code>&lt;div&gt;</code> 或者 <code>&lt;span&gt;</code> 会生成相应的字符串 'div' 或者 'span' 传递给 <code>React.createElement</code>(作为参数)。大写字母开头的元素则对应着在 JavaScript 引入或自定义的组件,如 <code>&lt;Foo /&gt;</code> 会编译为 <code>React.createElement(Foo)</code>。</p>
<h4 id="jsx-括号">JSX 括号</h4>
<p>官网示例中的 jsx 用括号包围起来。就像这样:</p>
<pre><code class="language-javascript">// 有括号
const element = (
&lt;h1 className="greeting"&gt;
    Hello, world!
&lt;/h1&gt;
);
</code></pre>
<p>感觉就是在写 html,而且还有缩进。</p>
<p>笔者尝试将括号去除:</p>
<pre><code class="language-javascript">const reactElem =
    &lt;h1 className="greeting"&gt;
      Hello, world!
    &lt;/h1&gt;

ReactDOM.render(
    reactElem,
    document.getElementById('root')
)
</code></pre>
<p>浏览器还是正常显示:“Hello, world!”。</p>
<p>我们建议将内容包裹在括号中,虽然这样做不是强制要求的,但是这可以避免遇到自动插入分号陷阱 —— 官网-JSX 简介</p>
<h3 id="jsx-小练习">JSX 小练习</h3>
<p>JavaScript 表达式可以被包裹在 <code>{}</code> 中作为子元素。例如,以下表达式是等价的:</p>
<pre><code class="language-javascript">&lt;MyComponent&gt;foo&lt;/MyComponent&gt;

&lt;MyComponent&gt;{'foo'}&lt;/MyComponent&gt;
</code></pre>
<p>例如将下面 ul 列表改为动态:</p>
<pre><code class="language-javascript">&lt;ul&gt;
    &lt;li&gt;finish doc&lt;/li&gt;
    &lt;li&gt;submit pr&lt;/li&gt;
    &lt;li&gt;nag dan to review&lt;/li&gt;
&lt;/ul&gt;
</code></pre>
<pre><code class="language-javascript">let todos = ['finish doc', 'submit pr', 'nag dan to review'];
const reactElem = (
    &lt;ul&gt;
      {
            todos.map(item =&gt; {
                return &lt;li&gt;{item}&lt;/li&gt;
            })
      }
    &lt;/ul&gt;
)
</code></pre>
<p>浏览器控制台告警:</p>
<pre><code class="language-javascript">Warning: Each child in a list should have a unique "key" prop.

警告:列表中的每个孩子都应该有一个唯一的“key”属性。
</code></pre>
<p>添加 <code>key</code> 即可:</p>
<pre><code class="language-javascript">todos.map((item, index) =&gt; {
    return &lt;li key={index}&gt;{item}&lt;/li&gt;
})
</code></pre>
<p><em>注</em>:用元素在数组中的下标作为 key,有时会存在问题 —— 正确使用 key</p>
<p><em>Tip</em>:相同功能,官网(JavaScript 表达式作为子元素)是这样实现的:</p>
<pre><code class="language-javascript">function Item(props) {
    return &lt;li&gt;{props.message}&lt;/li&gt;;
}

function TodoList() {
    const todos = ['finish doc', 'submit pr', 'nag dan to review'];
    return (
      &lt;ul&gt;
            {todos.map((message) =&gt; &lt;Item key={message} message={message} /&gt;)}
      &lt;/ul&gt;
    );
}

const reactElem = TodoList()
</code></pre>
<h3 id="组件">组件</h3>
<p>在 vue 中我们会这样使用组件:</p>
<pre><code class="language-javascript">// 注册组件
Vue.component('component-a', { /* ... */ })
Vue.component('component-b', { /* ... */ })
Vue.component('component-c', { /* ... */ })

new Vue({ el: '#app' })
</code></pre>
<pre><code class="language-javascript">&lt;div id="app"&gt;
&lt;component-a&gt;&lt;/component-a&gt;
&lt;component-b&gt;&lt;/component-b&gt;
&lt;component-c&gt;&lt;/component-c&gt;
&lt;/div&gt;
</code></pre>
<p><em>Tip</em>:仅作示意,通常我们会使用 spa 单页面应用开发。</p>
<p>于是可以将应用界面抽象成一棵组件树:</p>
<p><img src="https://images.cnblogs.com/cnblogs_com/blogs/665957/galleries/1950609/o_220312095329_%E7%BB%84%E4%BB%B6%E6%A0%91.png"></p>
<p>对比 vue 项目和react 项目的<strong>入口</strong>文件,其实都是将 <code>App</code> 组件挂载到 dom 元素上:</p>
<pre><code class="language-javascript">// vue项目/main.js
import App from './App.vue'
...
new Vue({
router,
store,
// 将 App 挂载到 #app
render: h =&gt; h(App)
}).$mount('#app')
</code></pre>
<pre><code class="language-javascript">// spug_web/src/index.js
import App from './App';
...

// 将 App 挂载到 #root
ReactDOM.render(
&lt;Router history={history}&gt;
    &lt;ConfigProvider locale={zhCN} getPopupContainer={() =&gt; document.fullscreenElement || document.body}&gt;
      &lt;App/&gt;
    &lt;/ConfigProvider&gt;
&lt;/Router&gt;,
document.getElementById('root')
);
</code></pre>
<h4 id="函数组件与-class-组件">函数组件与 class 组件</h4>
<p>定义组件<strong>最简单</strong>的方式是使用<code>函数</code>。请看示例:</p>
<pre><code class="language-javascript">&lt;script type="text/babel"&gt;
    // 函数组件
    function MyComponent(props) {
      return &lt;h1&gt;Hello, {props.name}&lt;/h1&gt;;
    }

    ReactDOM.render(
      &lt;div&gt;
            &lt;MyComponent name="peng" /&gt;
      &lt;/div&gt;,
      document.getElementById('root')
    );
&lt;/script&gt;
</code></pre>
<p>页面显示“Hello, peng”。组件对应的 html 为:<code>&lt;h1&gt;Hello, peng&lt;/h1&gt;</code>。</p>
<p>我们还可以使用 es6 的 <code>class</code> 来定义组件。就像这样:</p>
<pre><code class="language-javascript">// class 组件。
class MyComponent extends React.Component {
    // 必须定义 render()。否则会报错:
    // MyComponent(...): No `render` method found on the returned component instance: you may have forgotten to define `render`.
    render() {
      return &lt;h1&gt;Hello, {this.props.name}&lt;/h1&gt;
    }
}

// 省略。用法相同
</code></pre>
<p><em>Tip</em>:</p>
<ul>
<li>在 <code>React.Component</code> 的子类中,必须定义 <code>render()</code> 函数</li>
<li><strong>我们强烈建议你不要创建自己的组件基类</strong>。 在 React 组件中,代码重用的主要方式是组合而不是继承。 —— 官网</li>
<li>class 组件目前提供了更多的功能</li>
</ul>
<h4 id="组件中的-this">组件中的 this</h4>
<p>首先看函数组件中的 this:</p>
<pre><code class="language-javascript">// 函数组件
function MyComponent(props) {
+ console.log('this', this)
    return &lt;h1&gt;Hello, {props.name}&lt;/h1&gt;;
}
</code></pre>
<p>浏览器制台输出:<code>this undefined</code>。说明没有 this。</p>
<p>上文我们已经在 class 组件的 <code>render()</code> 中使用了 this,我们将其打印出来看一下:</p>
<pre><code class="language-javascript">// class 组件
class MyComponent extends React.Component {
    render() {
      + console.log('this', this)
      return &lt;h1&gt;Hello, {this.props.name}&lt;/h1&gt;
    }
}
</code></pre>
<pre><code class="language-javascript">this MyComponent {props: {…}, context: {…}, refs: {…}, updater: {…}, _reactInternals: FiberNode, …}
    context: {}
    props: {name: 'peng'}
    refs: {}
    state: null
    updater: {isMounted: ƒ, enqueueSetState: ƒ, enqueueReplaceState: ƒ, enqueueForceUpdate: ƒ}
    _reactInternalInstance: {_processChildContext: ƒ}
    _reactInternals: FiberNode {tag: 1, key: null, stateNode: MyComponent, elementType: ƒ, type: ƒ, …}
    isMounted: (…)
    replaceState: (…)
    []: Component
</code></pre>
<h5 id="为什么函数组件中的-this-是-undifined">为什么函数组件中的 this 是 undifined</h5>
<p>将函数组件 MyComponent 放入 bable 的试一试中,会被翻译成:</p>
<pre><code class="language-javascript">"use strict";

function MyComponent(props) {
console.log('this', this);
return /*#__PURE__*/React.createElement("h1", null, "Hello, ", props.name);
}
</code></pre>
<p><em>注</em>:jsx 被 babel 识别了处理啊,因为翻译成了 <code>React.createElement</code>。</p>
<p>严格模式下,如果没有指定 this 的话,它值是 <code>undefined</code>。</p>
<p>我们将 class 组件 MyComponent 也翻译一下:</p>
<pre><code class="language-javascript">"use strict";

class MyComponent extends React.Component {
render() {
    console.log('this', this);
    return /*#__PURE__*/React.createElement("h1", null, "Hello, ", this.props.name);
}

}
</code></pre>
<h3 id="props">props</h3>
<p>我们首先回忆一下 vue 中的 props:</p>
<ul>
<li>props 用于接收来自父组件的数据。就像这样:</li>
</ul>
<pre><code class="language-javascript">&lt;div id='app'&gt;
    &lt;button-counter :msg='message'&gt;&lt;/button-counter&gt;
&lt;/div&gt;

&lt;script&gt;
Vue.component('button-counter', {
    props: ['msg'],
    template: `&lt;div&gt;
                来自父组件的信息: {{msg}}
            &lt;/div&gt;`
})

var app = new Vue({
    el: '#app',
    data: {
      message: 'hello'
    }
})
&lt;/script&gt;
</code></pre>
<ul>
<li>可以使用.sync 修饰符来实现一个 prop 进行“双向绑定”。即子组件不要直接更改父组件的这个属性,而应该通知父组件,让父组件自己去更改这个属性。</li>
<li>prop 验证。就像这样:</li>
</ul>
<pre><code class="language-javascript">Vue.component('my-component', {
props: {
    // 基础的类型检查 (`null` 和 `undefined` 会通过任何类型验证)
    propA: Number,
    // 必填的字符串
    propC: {
      type: String,
      required: true
    },
    // 带有默认值的数字
    propD: {
      type: Number,
      default: 100
    },
    // 带有默认值的对象
    propE: {
      type: Object,
      // 对象或数组默认值必须从一个工厂函数获取
      default: function () {
      return { message: 'hello' }
      }
    },
    ...
}
})
</code></pre>
<p>在 react 中,props 的作用也类似,即用于接收父组件传来的属性。</p>
<h4 id="props-基本用法">props 基本用法</h4>
<p>给组件 MyComponent 定义了两个 prop(属性):</p>
<ul>
<li><code>name</code>,字符串类型,默认值是 defaultName</li>
<li><code>say</code>,函数类型,必填</li>
</ul>
<pre><code class="language-javascript">&lt;body&gt;
    &lt;div id="root"&gt;&lt;/div&gt;

    &lt;script src="https://unpkg.com/react@17/umd/react.development.js"&gt;&lt;/script&gt;
    &lt;script src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"&gt;&lt;/script&gt;
    &lt;script src="https://unpkg.com/@babel/standalone/babel.min.js"&gt;&lt;/script&gt;
    &lt;!-- 使用 PropTypes 进行类型检查 --&gt;
    &lt;script src="https://unpkg.com/prop-types@15.6/prop-types.js"&gt;&lt;/script&gt;

    &lt;script type="text/babel"&gt;
      class MyComponent extends React.Component {
            render() {
                const { name, say } = this.props
                return &lt;h1&gt;Hello, {this.props.name}, {say()}&lt;/h1&gt;
            }
      }
      // 属性类型
      MyComponent.propTypes = {
            name: PropTypes.string,
            // 注:函数不是 function,而是 func
            // 函数类型,并且必填
            say: PropTypes.func.isRequired
      }
      // 默认值
      MyComponent.defaultProps = {
            name: 'defaultName'
      }

      const props2 = { say: () =&gt; 'I know you' }

      ReactDOM.render(
            &lt;div&gt;
                &lt;MyComponent name="peng" say={() =&gt; 'I love you'} /&gt;
                &lt;MyComponent {...props2} /&gt;
            &lt;/div&gt;,
            document.getElementById('root')
      );
    &lt;/script&gt;
&lt;/body&gt;
</code></pre>
<p>页面显示:</p>
<pre><code class="language-javascript">Hello, peng, I love you
Hello, defaultName, I know you
</code></pre>
<p>自 React v15.5 起,React.PropTypes 已移入另一个包中。请使用 <code>prop-types</code> 库 代替 —— 官网</p>
<p><em>Tip</em>:有关类型检测更多介绍请看 使用 PropTypes 进行类型检查</p>
<h5 id="props-的只读性">Props 的只读性</h5>
<p>组件无论是使用函数声明还是通过 class 声明,都决不能修改自身的 props。倘若我们尝试修改 props 属性,就像这样:</p>
<pre><code class="language-javascript">class MyComponent extends React.Component {
    render() {
      + this.props.name = 'aName'
      ...
    }
}
</code></pre>
<p>浏览器控制台将报错如下:</p>
<pre><code class="language-javascript">Inline Babel script:10 Uncaught TypeError: Cannot assign to read only property 'name' of object '#&lt;Object&gt;'

内联 Babel 脚本:10 未捕获的类型错误:无法分配给对象 '#&lt;Object&gt;' 的只读属性 'name'
</code></pre>
<h5 id="使用-static-优化类型检测">使用 static 优化类型检测</h5>
<p>上文中,类型检测的相关代码,从语法上来说就是给 MyComponent 类增加两个静态属性:</p>
<pre><code class="language-javascript">// 类型检测相关代码
MyComponent.propTypes = {
    name: PropTypes.string,
    say: PropTypes.func.isRequired
}
MyComponent.defaultProps = {
    name: 'defaultName'
}
</code></pre>
<p>我们可以使用 static 语法来对其优化。就像这样:</p>
<pre><code class="language-javascript">// class 组件
class MyComponent extends React.Component {
    // 属性类型
    static propTypes = {
      name: PropTypes.string,
      say: PropTypes.func.isRequired
    }
    // 默认值
    static defaultProps = {
      name: 'defaultName'
    }
    render() {
      ...
    }
}
</code></pre>
<h4 id="函数组件中的-props">函数组件中的 props</h4>
<p>上文我们已经使用过了:</p>
<pre><code class="language-javascript">function MyComponent(props) {
    return &lt;h1&gt;Hello, {props.name}&lt;/h1&gt;;
}
</code></pre>
<p>虽然函数组件中没有 this,不能像 class 组件那样通过 this 去使用 props(<code>this.props.name</code>),但函数组件可以通过参数接收 props。</p>
<h4 id="superprops">super(props)</h4>
<p>在 React 组件挂载之前,会调用它的构造函数。在为 React.Component 子类实现构造函数时,应在其他语句之前调用 super(props)。否则,this.props 在构造函数中可能会出现未定义的 bug —— 官网</p>
<p>这句话什么意思?请看示例:</p>
<pre><code class="language-javascript">class MyComponent extends React.Component {
    constructor(props) {
      super(props)
      // 输出 {}。表明 this.props 有值
      console.log(this.props.name)
    }
    render() {
      ...
    }
}
</code></pre>
<p>将 <code>super(props)</code> 改为 <code>super()</code>:</p>
<pre><code class="language-javascript">class MyComponent extends React.Component {
    constructor(props) {
      super()
      // 输出 undefined
      console.log(this.props)
    }
    render() {
      ...
    }
}
</code></pre>
<blockquote>
<p>其他章节请看:</p>
<p>七天接手react项目 系列</p>
</blockquote>


</div>
<div id="MySignature" role="contentinfo">
    <section class="m-reprintStatement" style="white-space:normal;/* 防止break-all与nowrap矛盾 */word-break:break-all;">
    作者:彭加李<br>
    出处:https://www.cnblogs.com/pengjiali/p/16002317.html<br>
    本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接。
</section><br><br>
来源:https://www.cnblogs.com/pengjiali/p/16002317.html
頁: [1]
查看完整版本: 七天接手react项目 系列 —— react 起步