来源: 听说后端的你在学 React
- JQuery 解决了浏览器兼容和 DOM 元素快捷操作问题,其链式操作 API 也对后续前端框架产生了深刻影响;
- Knockout 提出了前端代码 MVVM 分层理念,数据通过模板映射为 UI 视图,大幅度减少了 DOM 操作;
- AngularJS 在 MVVM 基础上引入了双向绑定,数据变化自动反映到 UI,视图上的操作也反向自动更新数据;其通过指令拓展 HTML 的风格提升了模板引擎的灵活性,可惜作者引入了大量借鉴服务器编程的概念,让 AugularJS 学习成本直线上升,性能也略有不足;
- JSX,使用 JavaScript 表达 UI + 交互,充分利用 JavaScript 的灵活性;
- fx(props) = UI,数据驱动 UI,单向数据流、函数风格的页面组件;
-
Virtual DOM,服务器、客户端使用同一套代码渲染——同构,解决前端应用 SEO 问题;
npx create-react-app learn-react --template typescript
cd learn-react
npm start
执行 npm start后浏览器会在 http://localhost:3000 打开项目首页。
import React, { useState } from 'react';
function Button(props: { count: number }): JSX.Element {
const [count, setCount] = useState(props.count);
return (
<button
onClick={() => {
setCount((c) => c + 1);
}}
>
{count}
</button>
);
}
function App() {
const [count, setCount] = useState(0);
return (
<div className="App">
<Button count={5} />
</div>
);
}
export default App;
index.tsx
import React from 'react';
import * as ReactDOMClient from 'react-dom/client';
import App from './app';
const rootElement = document.querySelector('body') as Element;
const root = ReactDOMClient.createRoot(rootElement);
root.render(<App />);
打开 Chrome Dev Tools 可以看到多了一个 Components选项卡
<div className="container">
<CustomComponent
onClick={() => {alert('Hello')}}
>
Hello {props.name}!
</CustomComponent>
</div>
传统页面内容主要由 HTML 定义,JavaScript 逻辑是点缀,随着现代网页交互性增强,页面内容很大程度是由 JavaScript 逻辑动态生成,同时渲染逻辑本质上与其他 UI 逻辑内在耦合,比如在 UI 中需要绑定处理事件、在某些时刻状态发生变化时需要通知到 UI,以及需要在 UI 中展示准备好的数据。
因此 React 使用 JSX 把渲染逻辑和 HTML 标签集成到一起。
这样开发者关注的不是 HTML 模板、JavaScript 渲染逻辑这样的技术实现,而是诸如 Sidebar、Form 这样的页面功能单元。
/**
* JSX 语法隐式调用 React.createElement
* 所以虽然代码中没有调用 React 的语句,仍然需要引入
*/
import React from 'react';
interface IButton {
/** 按钮展示文案 */
text: string;
/** 点击按钮跳转链接 */
link?: string;
/** 点击按钮自定义事件 */
onClick?: (event?: Event) => void
}
function Button(props: IButton) {
const { text, link, onClick } = props;
const redirectHandler = () => {
location.href = link;
};
return (
<div
className="button"
onClick={onClick | redirectHandler}
>
{text}
</div>
);
}
export default Button;
在使用组件时候,通过其标签的属性组装成 props 对象,传递给组件,语法和 HTML attribute 类似,但值可以是任意的 JavaScript 对象。
import React from 'react';
/**
* 导入 ./button.tsx 中 export 的默认内容,命名为 Button 使用
* .tsx 拓展名可以省略
*/
import Button from './button';
interface IDialog {
title: string;
content: Element;
showClose: boolean;
}
function Dialog(props: IDialog) {
const { title, content, showClose = false, children } = props;
const hideDialog = () => {
// ...
}
return (
<div>
<div className="dialog-title"> {title} </div>
<div className="dialog-body"> {content | children} </div>
{/* 没错,Button props 定义的属性,就是这样通过标签属性开放出来的 */}
<Button
title="取消"
onClick={hideDialog}
/>
<Button
title="确认"
onClick={() => { }}
/>
</div>
);
}
export default Dialog;
组件写好后通过 react-dom [3]将组件渲染到页面。
import React from 'react';
import ReactDOM from 'react-dom/client';
import Dialog from './dialog';
// 把组件渲染到页面 id 为 root 的元素中
const rootElement = document.getElementById('root');
const root = ReactDOM.createRoot(rootElement);
root.render(
<Dialog
title="demo dialog"
content="this is a dialog"
showClose={false}
/>
);
- 组件名称使用 Pascal 风格(首字母大写),以和 HTML 原生标签(div、p、a 等)区分;
- 组件仅接受 props一个参数,用来暴露组件可配置属性,其子组件被 React 通过 children属性注入;
- 在组件内部 props 是只读的,不允许对其进行修改;
{/* 非法的 JSX */}
<div id="box1"></div>
<div id="box2"></div>
{/* 合法的 JSX */}
<>
<div id="box1"></div>
<div id="box2"></div>
</>
<meta charset="UTF-8">
<br>
<img src="https://g.alicdn.com/logo.png">
<>
<meta charset="UTF-8" />
<br/>
<img src="https://g.alicdn.com/logo.png"/>
</>
- 在 React 中常用的 DOM 特性和属性(包括事件处理)都使用小驼峰命名的方式,例如与 HTML 中的 tabindex 属性对应的 React 的属性是 tabIndex;
-
HTML 部分属性名称与 JavaScript 保留字冲突,在 JSX 中需要使用替代名称; - style 属性 value 是一个 CSS 属性组成的对象,为了让其符合 JavaScript 语法规则,属性名使用驼峰命名(fontSize、backgroundColor),而不是 CSS 属性使用的连字符,这样可以很方便设置动态样式,但静态样式应该依赖 className 和 CSS 文件的配合;
function HelloWorldComponent(props) {
const divStyle = {
// 可以很方便设置动态样式
backgroundImage: 'url(' + props.imgUrl + ')',
// 但静态样式应该尽量通过 className 设置类,通过 css file 解决
// 不推荐 color: 'blue' 这种静态样式直接在 JSX 的写法
color: 'blue',
};
return (
<div style={divStyle}>
Hello World!
</div>
);
}
-
React 对于 Form 表单支持 defaultValue 属性,设置默认值,在运行时取值使用和 HTML 一致的 value属性。
const content = `
这里应该展示一张图片<br>
<img src="https://sc02.alicdn.com/kf/HTB1gUuPUkzoK1RjSZFl761i4VXaw.png" />
`;
<div>
{content}
</div>
页面效果:
const content = `
这里应该展示一张图片<br>
<img src="https://sc02.alicdn.com/kf/HTB1gUuPUkzoK1RjSZFl761i4VXaw.png" />
`;
<div dangerouslySetInnerHTML={{ __html: content }}/>
- {变量名}读取变量值,双层 {{}} 并不是特殊语法,而是 {对象} 的快捷写法
<div style={{ color: 'red' }}></div>
// 等同于
const styleObj = { color: 'red' };
<div style={styleObj}></div>
- 三元表达式处理 if-else(if-else 是语句,不是表达式)
-
map 处理循环逻辑,批量生成元素
interface IStuff {
name: string;
sex: 'male' | 'female';
}
function App () {
const list: Array<IStuff> = [
{ name: 'Byron', sex: 'male' },
{ name: 'Casper', sex: 'male' },
{ name: 'Junice', sex: 'female' },
];
return (
<ul className="stuff-list">
{
list.map(stuff => { // 生成多个
const { name, sex } = stuff;
return (
{
<li
/* 实际编程 className 设置有更好的表达方式,这里仅 demo 三元表达式使用 */}
className={sex === 'male' ? 'stuff-male' : 'stuff-female'}
onClick={() => { alert(name) }}
>
// 读取变量值
{name}
</li>
);
})
}
</ul>
);
}
JSX 中注释也需要使用 {} 包裹,但这种写法过于不方便,大部分编译工具都可以处理双斜线风格//注释
<button id="9527" className="btn-primary">
<span style={{ color: 'red' }}>
This is a Button
</span>
</button>
JSX 用类似这样的结构表达:
{
"type": "button",
"props": {
"id": "9527",
"className": "btn-primary",
"children": [
{
"type": "span",
"props": {
"style": { "color": "red" },
"children": "This is a Button"
}
}
]
}
}
编译后实际是这样的调用:
React.createElement("button", {
id: "9527",
className: "btn-primary"
},React.createElement("span", {
style: {
color: 'red'
}
}, "This is a Button"));
React.createElement(type, props, …children),上文提到过 React 会自动把 children 注入到 props,就是在这个过程。
参考链接:
[1]https://create-react-app.dev/
[2]https://chrome.google.com/webstore/detail/react-developer-tools/fmkadmapgofadopljbjfkapdkoienihi
[3]https://www.npmjs.com/package/react-dom