目录
简介
React
React是一个用于构建用户界面的javaScript库,适用于用 JavaScript 构建快速响应的大型 Web 应用程序。
在实际项目开发中,可以按需引入React特性。
可以通过js文件引入的方式使用React。如果需要加载指定版本的 react 和 react-dom,可以把下面的 17 替换成所需加载的版本号。
开发环境引入示例:
<script src="https://unpkg.com/react@17/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@17/umd/react-dom.development.js" crossorigin></script>
生产环境引入示例:
<script src="https://unpkg.com/react@17/umd/react.production.min.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@17/umd/react-dom.production.min.js" crossorigin></script>
如果通过 CDN 的方式引入 React,建议如上所示设置 crossorigin 属性,以及验证使用的 CDN 是否设置了 Access-Control-Allow-Origin: * HTTP 请求头【可通过浏览器控制台里验证】。
一个React在线代码编辑器:
https://codesandbox.io/s/new?file=/src/App.js
引入JSX
JSX是一个 JavaScript 的语法扩展,它具有 JavaScript 的全部功能。JSX 可以生成 React “元素”。
React 不强制要求使用 JSX,但是建议在 React 中配合使用 JSX,因为JSX 可以很好地描述 UI 应该呈现出的它应有交互的本质形式。在 JavaScript代码中将 JSX 和 UI 放在一起时,会在视觉上有辅助作用。它还可以使 React 显示更多有用的错误和警告消息。
可以通过js文件引入的方式快速使用JSX,但是引入的JSX文件会使网站变慢,并且不适用于生产环境。
<script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>
将JSX引入项目里更好的方式是使用JSX预处理器来自动转换所有 <script> 标签的内容。
将 JSX 添加到项目中并不需要诸如打包工具或开发服务器那样复杂的工具。本质上,添加 JSX 就像添加 CSS 预处理器一样。唯一的要求是在计算机上安装了 Node.js。
这里使用 npm 只是用来安装 JSX 预处理器。React 和应用程序代码都可以继续使用 <script> 标签而不做任何更改。
在终端上跳转到项目文件夹,然后粘贴这两个命令:
步骤 1: 执行 npm init -y
步骤 2: 执行 npm install babel-cli@6 babel-preset-react-app@3
至此已为项目加入了一个生产就绪(production-ready)的 JSX 配置环境。
运行JSX
在项目目录下创建名为 js和jsx 的文件夹,并执行下面这个终端命令:
npx babel --watch jsx --out-dir ./js --presets react-app/prod
不要等待它运行结束 —— 这个命令启动了一个对 JSX 的自动监听器。
如果此时在jsx文件夹目录下新建一个js文件,监听器会在js文件夹目录下创建一个预处理过的相同名字的js 文件,它包含了适用于浏览器的普通 JavaScript 代码。当编辑受监听的带有 JSX 的源文件时,转换过程将自动重新执行。
这样,在旧浏览器上也能够使用现代 JavaScript 的语法特性,比如 class。刚才使用的工具叫 Babel。
注意:
npx 不是拼写错误 —— 它是 npm 5.2+ 附带的 package 运行工具。
React 工具链
使用集成的工具链,可以实现更好的用户和开发人员体验。它们有助于完成如下任务:
扩展文件和组件的规模。
使用来自 npm 的第三方库。
尽早发现常见错误。
在开发中实时编辑 CSS 和 JS。
优化生产输出。
一组 JavaScript 构建工具链通常由这些组成:
(1).一个 package 管理器,比如 Yarn 或 npm。它能帮助充分利用庞大的第三方 package 的生态系统,并且轻松地安装或更新它们。
(2).一个打包器,比如 webpack 或 Parcel。它能帮助编写模块化代码,并将它们组合在一起成为小的 package,以优化加载时间。
(3).一个编译器,例如 Babel。它能帮助编写的新版本 JavaScript 代码在旧版浏览器中依然能够工作。
使用工具链可能导致一个问题,就是引入、依赖的文件包过多,导致项目体积到后面很大【例如2GB】。
Create React App
Create React App【要求Node >= 10.16 和 npm >= 5.6】可用于学习 React 或者用 React 创建新的单页应用。它不会处理后端逻辑或操纵数据库,在内部使用了 Babel 和 webpack。当准备好部署到生产环境时,执行 npm run build 会在 build 文件夹内生成应用的优化版本。
要创建项目,可以执行:
npx create-react-app my-app
cd my-app
npm start
Next.js
Next.js 是一个流行的、轻量级的框架,用于配合 React 打造静态化和服务端渲染应用。它包括开箱即用的样式和路由方案,并且假定使用 Node.js 作为服务器环境。
Gatsby
Gatsby 是用 React 创建静态网站的很好的方式。它能使用 React 组件,但输出预渲染的 HTML 和 CSS 以保证最快的加载速度。
其它
如果是在打造组件库或将 React 集成到现有代码仓库,可尝试更灵活的工具链:
(1).Neutrino 把 webpack 的强大功能和简单预设结合在一起。并且包括了 React 应用和 React 组件的预设。
(2).Nx 是针对全栈 monorepo 的开发工具包,其内置了 React,Next.js,Express 等。
(3).Parcel 是一个快速的、零配置的网页应用打包器,并且可以搭配 React 一起工作。
(4).Razzle 是一个无需配置的服务端渲染框架,但它提供了比 Next.js 更多的灵活性。
概念
Hello World实例
学习新的编程技术时,可以将代码运行后能够在屏幕上输出“Hello World.”作为第一个简单的程序来编写,因为它的语句书写简单,如果能正确运行出结果,则表示基础运行环境已经安装好了,且已经能够符合预期地实现最基础的输出字符串语法功能。
代码示例
index.html:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- 上述3个meta标签*必须*放在最前面,任何其他内容都*必须*跟随其后! -->
<title>Basic</title>
<!-- 加载 React。-->
<!-- 注意: 部署时,将 "development.js" 替换为 "production.min.js"。-->
<script src="https://unpkg.com/react@17/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@17/umd/react-dom.development.js" crossorigin></script>
<!-- 引入的JSX文件会使网站变慢,并且不适用于生产环境。 -->
<!-- <script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script> -->
<!-- HTML5 shim 和 Respond.js 是为了让 IE8 支持 HTML5 元素和媒体查询(media queries)功能 -->
<!-- 警告:通过 file:// 协议(就是直接将 html 页面拖拽到浏览器中)访问页面时 Respond.js 不起作用 -->
<!--[if lt IE 9]>
<script src="https://cdn.jsdelivr.net/npm/html5shiv@3.7.3/dist/html5shiv.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/respond.js@1.4.2/dest/respond.min.js"></script>
<![endif]-->
</head>
<body>
<div id="test"></div>
<script src="./js/index.js"></script>
</body>
</html>
./jsx/index.js:
ReactDOM.render(
<h1>Hello, world!</h1>,
document.getElementById('test')
);
接着在命令行弹窗里的当前项目路径下运行以下命令
npx babel --watch jsx --out-dir ./js --presets react-app/prod
然后就可以在项目的js文件夹里看到经过预处理后的包含适用于浏览器的普通 JavaScript代码版本的index.js,这个生成后的js也就是本例index.html引入的js。
./js/index.js:
ReactDOM.render(React.createElement(
'h1',
null,
'Hello, world!'
), document.getElementById('test'));
在浏览器访问index.html能看到正常输出“Hello World.”就表明第一个程序完成了。
运行效果图:
JSX的使用
在 JSX 语法中,可以在大括号内放置任何有效的 JavaScript 表达式。例如,2 + 2,user.firstName 或 formatName(user) 都是有效的 JavaScript 表达式。
在编译之后,JSX 表达式会被转为普通 JavaScript 函数调用,并且对其取值后得到 JavaScript 对象。
在属性中嵌入 JavaScript 表达式时,不要在大括号外面加上引号。应该仅使用引号(对于字符串值)或大括号(对于表达式)中的一个,对于同一属性不能同时使用这两种符号。如果对表达式用引号可能会出现不符合预期的结果【例如列表中的key="item.id"取不到正确值,而key={item.id}可以去掉正确值】。
因为 JSX 语法上更接近 JavaScript 而不是 HTML,所以 React DOM 使用 camelCase(小驼峰命名)来定义属性的名称,而不使用 HTML 属性名称的命名约定。
例如,JSX 里的 class 变成了 className,而 tabindex 则变为 tabIndex。
可以安全地在 JSX 当中插入用户输入内容,React DOM 在渲染所有输入内容之前,默认会进行转义。它可以确保在应用中,永远不会注入那些并非自己明确编写的内容。所有的内容在渲染之前都被转换成了字符串。这样可以有效地防止 XSS(cross-site-scripting, 跨站脚本)攻击。
Babel 会把 JSX 转译成一个名为 React.createElement() 函数调用,React.createElement() 会预先执行一些检查,以帮助编写无错代码,但实际上它创建了一个对象,这样的对象被称为 “React 元素”。它们描述了希望在屏幕上看到的内容。React 通过读取这些对象,然后使用它们来构建 DOM 以及保持随时更新。
代码示例
jsx/index.js【JSX代码预处理前】:
function getString(userinfo){
if(userinfo.name){
return <p>{userinfo.name} 今年已经{userinfo.age}岁了。</p>
}
return <p>您还没有登录哦,登录后就可以看到个人信息了。<a className="tologin" href="/login.html">点击登录</a></p>
}
const userinfo={
name:'大明',
age:20
}
const template=(
getString(userinfo)
)
ReactDOM.render(
template,
document.getElementById("test")
)
js/index.js【JSX代码预处理后】:
function getString(userinfo) {
if (userinfo.name) {
return React.createElement(
"p",
null,
userinfo.name,
" \u4ECA\u5E74\u5DF2\u7ECF",
userinfo.age,
"\u5C81\u4E86\u3002"
);
}
return React.createElement(
"p",
null,
"\u60A8\u8FD8\u6CA1\u6709\u767B\u5F55\u54E6\uFF0C\u767B\u5F55\u540E\u5C31\u53EF\u4EE5\u770B\u5230\u4E2A\u4EBA\u4FE1\u606F\u4E86\u3002",
React.createElement(
"a",
{ className: "tologin", href: "/login.html" },
"\u70B9\u51FB\u767B\u5F55"
)
);
}
var userinfo = {
name: '大明',
age: 20
};
var template = getString(userinfo);
ReactDOM.render(template, document.getElementById("test"));
运行效果图:
如果将上例中index.js的userinfo设置为空字符串,会看到如下所示运行结果:
推荐在使用的编辑器中,使用 “Babel” 提供的语言定义,来正确地高亮显示 ES6 和 JSX 代码。
元素渲染
元素是构成 React 应用的最小砖块,元素描述了在屏幕上看到的内容。组件是由元素构成的。
与浏览器的 DOM 元素不同,React 元素是创建开销极小的普通对象。React DOM 会负责更新 DOM 来与 React 元素保持一致。
假设HTML 文件某处有一个 <div>,将其称为“根” DOM 节点,因为该节点内的所有内容都将由 React DOM 管理。
仅使用 React 构建的应用通常只有单一的根 DOM 节点,如果在将 React 集成进一个已有应用,那么可以在应用中包含任意多的独立根 DOM 节点。
想要将一个 React 元素渲染到根 DOM 节点中,只需把它们一起传入 ReactDOM.render()。
React 元素是不可变对象。一旦被创建,就无法更改它的子元素或者属性。一个元素就像电影的单帧:它代表了某个特定时刻的 UI。
更新 UI 的方式是创建一个全新的元素,并将其传入 ReactDOM.render()。
在实践中,大多数 React 应用只会调用一次 ReactDOM.render()。
React DOM 会将元素和它的子元素与它们之前的状态进行比较,并只会进行必要的更新来使 DOM 达到预期的状态。可以使用浏览器的检查元素工具查看实例运行来确认这一点。
代码示例
jsx/index.js【JSX代码预处理前】:
const timeZoneStr="东八区";
function showTime(){
const template=(
<div>
<p>当前所设置的时区为:{timeZoneStr}。</p>
<p>当前设置时区的时间显示为:{new Date().toString()}</p>
</div>
)
ReactDOM.render(template,document.getElementById("test"));
}
setInterval(showTime,1000);
js/index.js【JSX代码预处理后】:
var timeZoneStr = "东八区";
function showTime() {
var template = React.createElement(
"div",
null,
React.createElement(
"p",
null,
"\u5F53\u524D\u6240\u8BBE\u7F6E\u7684\u65F6\u533A\u4E3A\uFF1A",
timeZoneStr,
"\u3002"
),
React.createElement(
"p",
null,
"\u5F53\u524D\u8BBE\u7F6E\u65F6\u533A\u7684\u65F6\u95F4\u663E\u793A\u4E3A\uFF1A",
new Date().toString()
)
);
ReactDOM.render(template, document.getElementById("test"));
}
setInterval(showTime, 1000);
运行效果图:
组件 & Props
组件允许将 UI 拆分为独立可复用的代码片段,并对每个片段进行独立构思。
组件,从概念上类似于 JavaScript 函数。它接受任意的入参(即 “props”),并返回用于描述页面展示内容的 React 元素。定义组件最简单的方式就是编写 JavaScript 函数。
代码示例:
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}
该函数是一个有效的 React 组件,因为它接收唯一带有数据的 “props”(代表属性)对象与并返回一个 React 元素。这类组件被称为“函数组件”,因为它本质上就是 JavaScript 函数。
同时还可以使用 ES6 的 class 来定义组件,跟函数组件是等效的。
代码示例
jsx/index.js【JSX代码预处理前】:
class Welcome extends React.Component{
render(){
return <h1>Hello,{this.props.name}</h1>
}
}
const element=<Welcome name="大明" />;
ReactDOM.render(
<div>
{element}
</div>,
document.getElementById("test")
);
js/index.js【JSX代码预处理后】:
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }
function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
var Welcome = function (_React$Component) {
_inherits(Welcome, _React$Component);
function Welcome() {
_classCallCheck(this, Welcome);
return _possibleConstructorReturn(this, (Welcome.__proto__ || Object.getPrototypeOf(Welcome)).apply(this, arguments));
}
_createClass(Welcome, [{
key: "render",
value: function render() {
return React.createElement(
"h1",
null,
"Hello,",
this.props.name
);
}
}]);
return Welcome;
}(React.Component);
var element = React.createElement(Welcome, { name: "\u5927\u660E" });
ReactDOM.render(React.createElement(
"div",
null,
element
), document.getElementById("test"));
运行效果图:
React 元素除了是 DOM 标签,也可以是用户自定义的组件。
当 React 元素是用户自定义组件时,它会将 JSX 所接收的属性(attributes)以及子组件(children)转换为单个对象传递给组件,这个对象被称之为 “props”。
组件可以在其输出中引用其他组件,可以用同一组件来抽象出任意层次的细节。
代码示例
./jsx/index.js:
function Welcome(props){
return <p>你好,{props.name}</p>
}
function Alist(){
return <div>
<Welcome name="小明"></Welcome>
<Welcome name="中明"></Welcome>
<Welcome name="大明"></Welcome>
</div>
}
ReactDOM.render(
<Alist />,
document.getElementById("test")
)
运行效果图:
通常来说,每个新的 React 应用程序的顶层组件都是 App 组件。
在大型应用中,提取并构建可复用组件库是一种很好的做法。
代码示例
./jsx/index.js:
function UserAvatar(props){
return <img src={props.user.imgsrc} alt={props.user.name} />
}
function Userinfo(props){
return <div className="userInfo">
<div className="userAvatar">
<UserAvatar user={props.user} />
</div>
<p>{props.user.name}</p>
</div>
}
function UserComments(props){
return <div className="userComments">
<div className="baseInfo">
<div className="left">
<Userinfo user={props.user} />
</div>
<div className="right">
<span>{props.commenttime}</span>
</div>
<div className="clearfix"></div>
</div>
<div className="comments">
{props.comments}
</div>
</div>
}
const props={
user:{
name:'小明',
imgsrc:'https://ss0.baidu.com/94o3dSag_xI4khGko9WTAnF6hhy/zhidao/pic/item/b3b7d0a20cf431ad1b47ee904136acaf2fdd9886.jpg'
},
commenttime:'2021/08/12 12:22:50',
comments:'阿弥陀佛'
}
const element=<UserComments user={props.user} commenttime={props.commenttime} comments={props.comments} />;
ReactDOM.render(
<div>
{element}
</div>,
document.getElementById('test')
)
React 非常灵活,但它也有一个严格的规则:
所有 React 组件都必须像纯函数一样保护它们的 props 不被更改。
组件无论是使用函数声明还是通过 class 声明,都决不能修改自身的 props。组件代码里应该不更改入参,且多次调用下相同的入参始终返回相同的结果。
注意:
组件名称必须以大写字母开头。
React 会将以小写字母开头的组件视为原生 DOM 标签。例如,<div /> 代表 HTML 的 div 标签,而 <Welcome /> 则代表一个组件,并且需在作用域内使用 Welcome。
State & 生命周期
State 与 props 类似,但是 state 是私有的,并且完全受控于当前组件。
在具有许多组件的应用程序中,当组件被销毁时释放所占用的资源是非常重要的。
可以为 class 组件声明一些特殊的方法,当组件挂载或卸载时就会去执行这些方法。
尽管 this.props 和 this.state 是 React 本身设置的,且都拥有特殊的含义,但是可以向 class 中随意添加不参与数据流(比如计时器 ID)的额外字段。
正确地使用 State ,应该了解三件事:
(1). 不要直接修改 State,而是应该使用 setState()来修改State。
(2). 因为 this.props 和 this.state 可能会异步更新,所以不要依赖他们的值来更新下一个状态。
要解决这个问题,可以让 setState() 接收一个函数而不是一个对象。这个函数用上一个 state 作为第一个参数,将此次更新被应用时的 props 做为第二个参数。
(3). 当调用 setState() 的时候,React 会把提供的对象合并到当前的 state。
不管是父组件或是子组件都无法知道某个组件是有状态的还是无状态的,并且它们也并不关心它是函数组件还是 class 组件。这就是为什么称 state 为局部的或是封装的的原因。除了拥有并设置了它的组件,其他组件都无法访问。可以在有状态的组件中使用无状态的组件。
组件可以选择把它的 state 作为 props 向下传递到它的子组件中,这对于自定义组件同样适用。这通常会被叫做“自上而下”或是“单向”的数据流。任何的 state 总是所属于特定的组件,而且从该 state 派生的任何数据或 UI 只能影响树中“低于”它们的组件。
如果把一个以组件构成的树想象成一个 props 的数据瀑布的话,那么每一个组件的 state 就像是在任意一点上给瀑布增加额外的水源,但是它只能向下流动。
示例代码
./jsx/index.js:
class AClock extends React.Component{
constructor(props){
super(props),
this.state={date:new Date()}
}
tick(){
this.setState({date:new Date()});
}
componentDidMount(){
this.TimeID=setInterval(
()=>this.tick(),
1000
)
}
componentWillUnmount(){
clearInterval(this.TimeID);
}
render(){
return (
<div>
<p>当前设置时区为东八区</p>
<p>当前设置时区时间显示为{this.state.date.toString()}</p>
</div>
)
}
}
ReactDOM.render(
<AClock />,
document.getElementById("test")
)
运行效果图:
事件处理
React 事件的命名采用小驼峰式(camelCase),而不是纯小写。
使用 JSX 语法时需要传入一个函数作为事件处理函数,而不是一个字符串。
例如,传统的 HTML:
<button onclick="activateLasers()">
Activate Lasers
</button>
而在React里应该写为:
<button onClick={activateLasers}>
Activate Lasers
</button>
在 React 中另一个不同点是不能通过返回 false 的方式阻止默认行为。必须显式的使用 preventDefault 。
例如,传统的 HTML 中阻止链接默认打开一个新页面,可以这样写:
<a href="#" onclick="console.log('The link was clicked.'); return false">
Click me
</a>
在 React 中,可能是这样的:
function ActionLink() {
function handleClick(e) {
e.preventDefault();
console.log('The link was clicked.');
}
return (
<a href="#" onClick={handleClick}>
Click me
</a>
);
}
使用 React 时,一般不需要使用 addEventListener 为已创建的 DOM 元素添加监听器。事实上,只需要在该元素初始渲染的时候添加监听器即可。
代码示例
./jsx/index.js:
class ToggleButton extends React.Component{
constructor(props){
super(props),
this.state={
toggleStatus:true
},
this.funToggle=this.funToggleClick.bind(this)
}
funToggleClick(){
this.setState(State=>({
toggleStatus:!State.toggleStatus
}));
}
render(){
return (
<button onClick={this.funToggle} >
{this.state.toggleStatus?'ON':'OFF'}
</button>
);
}
}
ReactDOM.render(
<div>
<ToggleButton />
</div>,
document.getElementById("test")
)
在循环中,通常会为事件处理函数传递额外的参数。例如,若 id 是要删除那一行的 ID,以下两种方式都可以向事件处理函数传递参数:
<button onClick={(e) => this.deleteRow(id, e)}>Delete Row</button>
<button onClick={this.deleteRow.bind(this, id)}>Delete Row</button>
上述两种方式是等价的,分别通过箭头函数和 Function.prototype.bind 来实现。
在这两种情况下,React 的事件对象 e 会被作为第二个参数传递。如果通过箭头函数的方式,事件对象必须显式的进行传递,而通过 bind 的方式,事件对象以及更多的参数将会被隐式的进行传递。
条件渲染
在 React 中,可以创建不同的组件来封装各种需要的行为。然后,依据应用的不同状态,可以只渲染对应状态下的部分内容。
代码示例
./jsx/index.js:
function HasLogin(){
return <span>欢迎登录<a href="#">个人中心</a></span>;
}
function NotLogin(){
return <span>请先登录</span>
}
function LoginText(props){
if(props.isLogin){
return <HasLogin />;
}else{
return <NotLogin />;
}
}
function LoginBtnC(props){
return <button onClick={props.onClick}>点击登录</button>
}
function LogoutBtnC(props){
return <button onClick={props.onClick}>退出登录</button>
}
class LoginShow extends React.Component{
constructor(props){
super(props),
this.loginBtnClick=this.loginBtn.bind(this);
this.logoutBtnClick=this.logoutBtn.bind(this);
this.state={
loginStatus:false
}
}
loginBtn(){
this.setState({loginStatus:true});
}
logoutBtn(){
this.setState(state=>({loginStatus:false}));
}
render(){
let showDiv;
if(this.state.loginStatus){
showDiv=<LogoutBtnC onClick={this.logoutBtnClick} />
}else{
showDiv=<LoginBtnC onClick={this.loginBtnClick}/>
}
return (
<div>
<LoginText isLogin={this.state.loginStatus} />
{showDiv}
</div>
)
}
}
ReactDOM.render(
<LoginShow />,
document.getElementById("test")
)
通过花括号包裹代码,可以在 JSX 中嵌入任何表达式。
在极少数情况下,可能希望能隐藏组件,即使它已经被其他组件渲染。若要完成此操作,可以让 render 方法直接返回 null,而不进行任何渲染。
列表 & Key
一个元素的 key 最好是这个元素在列表中拥有的一个独一无二的字符串。
数组元素中使用的 key 在其兄弟节点之间应该是独一无二的。然而,它们不需要是全局唯一的。
不指定显式的 key 值,那么 React 将默认使用索引用作为列表项目的 key 值。
不建议使用索引来用作 key 值,因为这样做会导致性能变差,还可能引起组件状态的问题。
在 map() 方法中的元素需要设置 key 属性。
示例代码
./jsx/index.js:
function Articles(props){
const sideBar=(props.dataarr.map((item)=>
<li key={item.id}>{item.title}</li>
));
//console.log(sideBar);
const contentList=props.dataarr.map((item)=>
<li key={item.id}>
<h3>{item.title}</h3>
<p>{item.content}</p>
</li>
);
return (
<div className="container">
<div className="left">
{contentList}
</div>
<div className="right">
{sideBar}
</div>
</div>
);
}
const dataarr=[
{id:1,title:'测试文章一',content:'测试文章一里的文章内容,测试文本,测试文本'},
{id:2,title:'测试文章二',content:'测试文章二里的文章内容,测试文本,测试文本'},
{id:3,title:'测试文章三',content:'测试文章三里的文章内容,测试文本,测试文本'}
];
ReactDOM.render(
<Articles dataarr={dataarr} />,
document.getElementById('test')
)
key 会传递信息给 React ,但不会传递给自定义组件。如果自定义组件中需要使用 key 属性的值,请用其他属性名显式传递这个值。
代码示例:
//此例中,Post 组件可以读出 props.id,但是不能读出 props.key。
const content = posts.map((post) =>
<Post key={post.id} id={post.id} title={post.title} />
);
表单
在 HTML 中,表单元素(如<input>、 <textarea> 和 <select>)之类的表单元素通常自己维护 state,并根据用户输入进行更新。而在 React 中,可变状态(mutable state)通常保存在组件的 state 属性中,并且只能通过使用 setState()来更新。
可以把两者结合起来,使 React 的 state 成为“唯一数据源”。渲染表单的 React 组件还控制着用户输入过程中表单发生的操作。被 React 以这种方式控制取值的表单输入元素就叫做“受控组件”。
对于受控组件来说,输入的值始终由 React 的 state 驱动。也可以将 value 传递给其他 UI 元素,或者通过其他事件处理函数重置,但这意味着需要编写更多的代码。
在 HTML 中, <textarea> 元素通过其子元素定义其文本,而在 React 中,<textarea> 使用 value 属性代替。
以将数组传递到 value 属性中,以支持在 select 标签中选择多个选项:
<select multiple={true} value={['B', 'C']}>
在 HTML 中,<input type="file"> 允许用户从存储设备中选择一个或多个文件,将其上传到服务器,或通过使用 JavaScript 的 File API 进行控制。因为它的 value 只读,所以它是 React 中的一个非受控组件。
当需要处理多个 input 元素时,可以给每个元素添加 name 属性,并让处理函数根据 event.target.name 的值选择要执行的操作。
由于 setState() 自动将部分 state 合并到当前 state, 只需调用它更改部分 state 即可。
在受控组件上指定 value 的 prop 会阻止用户更改输入。如果指定了 value,但输入仍可编辑,则可能是意外地将value 设置为 undefined 或 null。
有时使用受控组件会很麻烦,因为需要为数据变化的每种方式都编写事件处理函数,并通过一个 React 组件传递所有的输入 state。当把之前的代码库转换为 React 或将 React 应用程序与非 React 库集成时,这可能会令人厌烦。在这些情况下,可能希望使用非受控组件, 这是实现输入表单的另一种方式。
如果想寻找包含验证、追踪访问字段以及处理表单提交的完整解决方案,使用 Formik 是不错的选择。然而,它也是建立在受控组件和管理 state 的基础之上 —— 所以不要忽视学习它们。
状态提升
通常,多个组件需要反映相同的变化数据,这时建议将共享状态提升到最近的共同父组件中去【子组件接收父组件的props】。
在 React 应用中,任何可变数据应当只有一个相对应的唯一“数据源”。通常,state 都是首先添加到需要渲染数据的组件中去。然后,如果其他组件也需要这个 state,那么可以将它提升至这些组件的最近共同父组件中。应当依靠自上而下的数据流,而不是尝试在不同组件间同步 state。
虽然提升 state 方式比双向绑定方式需要编写更多的“样板”代码,但带来的好处是,排查和隔离 bug 所需的工作量将会变少。由于“存在”于组件中的任何 state,仅有组件自己能够修改它,因此 bug 的排查范围被大大缩减了。此外,也可以使用自定义逻辑来拒绝或转换用户的输入。
如果某些数据可以由 props 或 state 推导得出,那么它就不应该存在于 state 中。
组合 vs 继承
React 有十分强大的组合模式。推荐使用组合而非继承来实现组件间的代码重用。
代码示例:
function SplitPane(props) {
return (
<div className="SplitPane">
<div className="SplitPane-left">
{props.left} </div>
<div className="SplitPane-right">
{props.right} </div>
</div>
);
}
function App() {
return (
<SplitPane
left={
<Contacts /> }
right={
<Chat /> } />
);
}
Props 和组合提供了清晰而安全地定制组件外观和行为的灵活方式。注意:组件可以接受任意 props,包括基本数据类型,React 元素以及函数。
如果想要在组件间复用非 UI 的功能,建议将其提取为一个单独的 JavaScript 模块,如函数、对象或者类。组件可以直接引入(import)而无需通过 extend 继承它们。