React 是目前前端大型项目的最佳解决组件化解决方案。
webpack 将所有资源都看成是模块的打包方式,让组件化开发思维有了落地实践的可能,React 则是在此基础上,彻底贯彻了组件化思维。
React 并非一个框架,而是一个 UI 库。它传达的是一种基于组件化的响应式开发思维。
理解并认知这一点非常重要,他决定了你学习 React 的方式是否正确。我们的重心应该是学习这种思维方式,许多人在学习 React 时,往往会忽略这一点,那么学习成果自然是不理想的
React 颠覆了传统的前端开发思维。在前端历史上具有里程碑的意义。
在前面章节的模块案例一中,我们使用原生代码结合发布订阅模式,基于模块化开发思维,也实现了一个响应式的交互。这里我们使用 React 重新实现一次看看差别如何。
效果:一堆控制按钮,改变 div 原生的宽高颜色等属性
首先使用 create react app 创建一个项目
npx create-react-app controldiv --template typescript
修改 src/app.css
的内容如下
10#control {20width: 500px;30margin: 100px auto;40}5060#control .target {70width: 200px;80height: 200px;90border: 2px solid #FFF;10background-color: #cccccc;11transition: 0.3s;12}13#control .target.hide {14display: none;15}
然后修改 src/App.tsx
如下
10import React, { useRef, useState } from 'react';20import './App.css';3040function App() {50const [bgColor, setBgColor] = useState('#FFF')60const [borderColor, setBorderColor] = useState('#ccc')70const [show, setShow] = useState(true)80const [width, setWidth] = useState(200)90const [height, setHeight] = useState(200)1011const bgColorInput = useRef<HTMLInputElement>(null)12const bdColorInput = useRef<HTMLInputElement>(null)1314function bgColorHandler() {15if (bgColorInput.current) {16setBgColor(bgColorInput.current.value)17}18}1920function bdColorHandler() {21if (bdColorInput.current) {22setBorderColor(bdColorInput.current.value)23}24}2526const style = {27backgroundColor: bgColor,28borderColor,29display: show ? 'block' : 'none',30width,31height32}3334return (35<div id="control">36<div className="control_wrap">37<div><button className="show" onClick={() => setShow(!show)}>show/hide</button></div>38<div>39<input ref={bgColorInput} className="bgcolor_input" type="text" placeholder="input background color" />40<button className="bgcolor_btn" onClick={bgColorHandler}>sure</button>41</div>42<div>43<input ref={bdColorInput} type="text" className="bdcolor_input" placeholder="input border color" />44<button className="bdcolor_btn" onClick={bdColorHandler}>sure</button>45</div>46<div>47<span>width:</span>48<button className="width_reduce" onClick={() => setWidth(width - 5)}>-5</button>49<button className="width_add" onClick={() => setWidth(width + 5)}>+5</button>50</div>51<div>52<span>height:</span>53<button className="height_reduce" onClick={() => setHeight(height - 5)}>-</button>54<input type="text" className="height_input" readOnly value={height} />55<button className="height_add" onClick={() => setHeight(height + 5)}>+</button>56</div>57</div>58<div className="target" style={style}></div>59</div>60);61}6263export default App;
这里我们要关注的核心是,当我们想要修改目标 div 的宽度时,我们仅仅只是使用了 setWidth()
这一句代码。
但实际上我们要做的事情还有很多,获取目标元素,设置目标元素的值等等,但是 React 帮助我们处理了这些事情,于是,当点击事件发生时,我们只需要关注数据的改变就可以了。
通过数据控制 UI 交互,是响应式交互思维的核心。
这极大的减少了我们的开发工作量,我们思考的内容也变得更少了。我们只需要维护好不同时刻的不同数据,就能够完成页面上的交互需求。
因此在实践中,我们会把与 UI 相关的状态与数据,单独使用 React 内部提供的 useState
或者 this.state
来管理。而其他的与 UI 渲染结果无关的数据,则采用别的方式来管理。
所有的客户端语言都有自己表达页面结构的方式,Android、iOS、Flutter、html 等。而 html 被公认为是最通俗易懂的表达页面结构的方式。
标签语言也提供了组件化的最佳呈现方式。
我们思考一下 html 标签,例如 button 元素。
<button>按钮</button>
button 还支持很多属性,disabled,name,value 等等,我们之前称之为 attribute
1<button name="btn">按钮1</button>2<button disabled>按钮2</button>
我们知道,不同的属性,对应了不同的功能,样式。也就是说,我们可以通过传入不同的属性控制 button 的能力。
与此同时,button 元素还有自己的内部逻辑。例如点击时,样式会发生改变。
所以,我们要完全的掌握 html 元素,明白这两个很核心的知识点,它们有内部的状态与逻辑,也可以通过外部传入属性控制元素的能力。这刚好就是一个组件应该呈现的样子。
React 组件借鉴 html 元素,并扩展 html 元素。
因此,一个最简单的 React 组件声明如下
1function Button() {2return (3<button>默认按钮</button>4)5}
该组件没有内部状态,也没有外部属性,因此使用时也很简单
<Button />
那如果我们要自定义一个更复杂的组件,就需要结合实际情况,分别考虑组件内部状态 state
,与组件外部属性 props
。
在 HTML 中,标签只是语法糖,之所以呈现这种结构是因为更容易理解元素之间的相互关系。一个标签元素的本质是对象。也就是说,只要我们在 html 中,写入一个标签,与之对应的,就是在实例化一个该标签的对象。
同样的道理,在 React 组件运用的 jsx 模板中,每一个标签元素都是一个对象。
当我们理解了这一点,在 React 中,使用时如下的写法就很容易理解了
const element = <div />
1function Welcome() {2return <h1>Hello, wrold!</h1>;3}
与 html 元素不同的是,React 提供了自定义组件的方式。因此我们可以在此基础上有更丰富的想象力。自定义组件是我们在实践开发中经常要干的事情。
当然,所有的自定义组件,本质都都是基于 html 元素组件。React 重新实现了这些 html 直接支持的元素。
刚才我们已经分析过,当我们要自定义复杂的组件时,需要考虑内部逻辑 state
与外部属性 props
。
本文主要的目的不是 React 技术细节的学习,因此我们只介绍函数式创建组件的方式,class 创建组件的方式大家另行学习
自定义一个考虑了 props 逻辑的组件。
1function Welcome(props) {2return <h1>Hello, {props.name}</h1>;3}
使用时
<Welcome name="world" />
自定义一个考虑了内部 state 逻辑的组件,
实现效果:按钮点击一次,按钮显示的数字 + 1
1function Button() {2const [number, setNumber] = useState(0)34return (5<button onClick={() => setNumber(number + 1)}>6{number}7</button>8)9}
使用时
<Button />
我们上面在定义 Button 组件时,往 button 中传入了属性 onClick,当然,我们也可以把 Button 内部的 number 传递给子组件。
10function Button() {20const [number, setNumber] = useState(0)3040return (50<div>60<button onClick={() => setNumber(number + 1)}>+ 1</button>70<Welcome name={number} />80</div>9010)11}
这种数据自上而下的传递过程,叫做单向数据流,数据的源头一定存在于某个组件的 state 中,并通过 props 传递。
任何 state 的修改,都会导致数据流动一次,数据流动时,UI 也会重新渲染一次。
因此,当我们想要让两个或者多个组件之间进行数据交互时,最好的方式就是找到这些组件共同的数据源「共同的父级」,在不同的组件之中,借助 props 的流动,通过修改数据源的 state,进行交互。
如果是父子组件,就把 state 存在父组件中。 如果是不相关的两个组件,就找这两个组件共同的父级组件。
推荐资源: