我就是被迷的那双眼。有时候需求来了,用熟悉的套路进行开发,确实很节省时间也能保证功能的稳定,但是这些开发的惯性无形中阻碍了我对技术的探索。
我一直想改造详情页,解放重复功能开发的劳动力,但是详情页一眼望都是内容平铺,好像并没有什么可做的代码设计。
后来我拨开繁花,发现详情页的组件化不必想的过于复杂,后台系统风格统一即可。因为大部分的详情页面是内容的展示,偶尔会出现少量的操作功能。将风格统一的部分进行组件化处理,操作功能使用回调函数放回当前页面,避免组件里做过多的业务逻辑。看,这不就成了。

项目基于React框架开发的,所以代码写法是JSX语法,组件开发使用的hooks函数式组件,UI框架使用的是antd。
开发前进行功能设计是我逐渐养成的一个良好习惯,有时候急于开发,可能漏掉一些设计细节或者功能。这次的详情页设计主要包括四个部分,UI组件、模块划分、数据重组、操作回调。
设计的功能如下:

其中操作回调是为了实现功能性操作按钮的功能,比如取消操作、审核操作、查看等详情页常见的操作按钮。
我捋了一下现有的业务,除了极个别的详情页设计的比较有自己的风格特点,其他基本都是包括2-n个模块展示数据,部分模块下会有操作按钮,某些模块下的某些数据项会有操作按钮,较长的页面会有快速定位导航等。
所以我会根据功能的复杂度递增,逐步的实现这个详情页UI组件。
注:前面功能实现我主要放关键代码,会把完整代码放在文章的末尾。
纯展示,根据接口返回的字段,重组数据,之所以用重组 数据的方式是因为某些数据需要特殊处理,比如时间数据,需要将时间戳转成日期格式;枚举数据,需要将返回值展示为具体文字。
假设当前详情页有四个模块:用户信息、订单信息、快递信息、支付信息。四个模块内容展示有相似有不同,但是依旧可以把展示方式分成两种:一排两个的平铺展示和Table表格展示。
模块划分完成之后,页面呈现在脑海中也有了大致的结构。第一个明确的设计点也就有了,既然模块展示具有相似性。我就可以把UI渲染设计成数组循环的方式。对于不同的展示方式,可以根据模块的key值去区分定义展示类型。
详情页
/**
* @description 详情页
*/
import React, { useState, useEffect } from 'react';
......
import { ORDER_COLUMNS } from '@/constants/detailBase';
const DetailBase = () => {
/** @name 页面内容数组对象 */
let dataListInit = [
{
key: 'userInfo', // 模块key值
name: '用户信息', //模块标题
},
{
key: 'orderList',
name: '订单信息',
columns: _.cloneDeep(ORDER_COLUMNS),
},
{
key: 'postInfo',
name: '快递信息',
},
{
key: 'payInfo',
name: '支付信息',
},
];
// 列表数据重组
dataListInit.map(item => {
item.list = []; // 模块展示内容数组
item.contentType = 'row'; // 展示形式类型 row-平铺 table-表格
// =>true: 订单信息 展示为表格
if (item.key === 'orderList') {
item.contentType = 'table';
}
});
let [dataList, setDataList] = useState(dataListInit);
return <></>;
};
export default DetailBase;
详情页常量
对于常量管理,一般会放到常量文件中。
/**
* @description 详情页
*/
import React, { useState, useEffect } from 'react';
......
const DetailBase = () => {
......
/**
* 用户信息-展示数据重组
* @param {Object} data 需要获取的项的对象
* @return {Object} 获得的值
*/
const getUserData = data => {
let list = [
{
name: '姓名',
value: data.name,
},
{
name: '年龄',
value: data.age,
},
{
name: '电话',
value: data.phone,
},
{
name: '收货地址',
value: data.address,
},
];
return list;
};
/**
* 快递信息-展示数据重组
* @param {Object} data 需要获取的项的对象
* @return {Object} 获得的值
*/
const getPostData = data => {
let list = [
{
name: '付款单号',
value: data.postNum,
},
{
name: '付款公司',
value: data.postName,
},
];
return list;
};
/**
* 支付信息-展示数据重组
* @param {Object} data 需要获取的项的对象
* @return {Object} 获得的值
*/
const getPayData = data => {
let list = [
{
name: '付款时间',
value: data.payAt ? moment(data.payAt).format('YYYY-MM-DD HH:mm:ss') : '',
},
{
name: '付款金额',
value: data.payMoney,
},
{
name: '操作时间',
value: data.payOperateAt ? moment(data.payOperateAt).format('YYYY-MM-DD HH:mm:ss') : '',
},
];
return list;
};
/**
* 获取列表项的实际值
* @param {Object} item 需要获取的项的对象
* @param {Object} res 接口请求数据
* @return {Object} 获得的值
*/
const getItemList = (item, data) => {
let obj = {
userInfo: getUserData(data),
postInfo: getPostData(data),
payInfo: getPayData(data),
};
return obj[item.key];
};
/**
* 初始化数据
*/
const initData = () => {
// 请求接口获取返回值
let res = {
userInfo: {
name: '张三',
age: 30,
phone: '12345678912',
address: '北京市朝阳区',
},
payInfo: {
payAt: 1641039600000,
payMoney: 999,
payOperateAt: 1641038400000,
},
orderList: [
{
name: '跑鞋·追光者',
color: '白色',
creatAt: 1641038400000,
payAt: 1641039600000,
haveFreight: 1,
},
{
name: '运动裤·逐梦',
color: '黑色',
creatAt: 1641038400000,
payAt: 1641039600000,
haveFreight: 1,
},
{
name: '外套·闪光者',
color: '蓝色',
creatAt: 1641038400000,
payAt: 1641039600000,
haveFreight: 1,
},
],
postInfo: {
postName: '顺丰',
postNum: '1111',
},
};
let list = _.cloneDeep(dataListInit);
// 数据重置
list.map(item => {
if (item.contentType === 'table') {
item.list = res[item.key];
} else {
let data = res[item.key];
item.list = getItemList(item, data);
}
});
setDataList(list);
};
useEffect(() => {
initData();
}, []);
return <></>;
};
export default DetailBase;
因为是根据业务进行的功能设计,所以我把详情组件放到了业务组件下面。
/**
* @description 公共业务组件-详情
*/
import React from 'react';
import PropTypes from 'prop-types';
import { Card, Row, Col, Table } from 'antd';
const CommonDetailBase = ({ ...props }) => {
const { dataList } = props;
/**
* row类页面内容回显
* @param {Object} data 展示内容对象
* @return {Element} 展示内容
*/
const dataRowContent = data => {
const list = data.list ? data.list : [];
return (
<>
{list.map((rowItem, rowIndex) => {
return (
<Col span={12} key={rowIndex}>
<Card size='small'>
<div>
{rowItem.name}:{rowItem.value}
</div>
</Card>
</Col>
);
})}
</>
);
};
/**
* Table类页面内容回显
* @param {Object} item 展示内容对象
* @return {Node} 展示内容
*/
const dataTableContent = item => {
let list = item.list ? item.list : [];
return <Table dataSource={list} columns={item.columns} rowKey={record => record.id} pagination={false} size='small' />;
};
return (
<div>
<div className='view-content'>
{dataList.map(item => {
return (
<Card type='inner' title={item.name} id={item.key} key={item.key} className='mb20'>
{item.contentType === 'row' ? <Row gutter={[12, 12]}>{dataRowContent(item)}</Row> : null}
{item.contentType === 'table' ? dataTableContent(item) : null}
{item.moduleBottomName ? (
<Button type='primary' onClick={() => item.moduleBottomView(item)} className='mt20'>
{item.moduleBottomName}
</Button>
) : null}
</Card>
);
})}
</div>
</div>
);
};
CommonDetailBase.propTypes = {
dataList: PropTypes.array, // 页面展示数组对象
};
CommonDetailBase.defaultProps = {
dataList: [],
};
export default CommonDetailBase;
页面使用
/**
* @description 详情页
*/
import React, { useState, useEffect } from 'react';
......
// 引入组件
import { CommonDetailBase } from '@/bundleComponents';
......
const DetailBase = () => {
return <CommonDetailBase dataList={dataList} />;
};
export default DetailBase;
所谓升级款,即在原来的基础上功能更丰富。比如我们的业务需求,模块下面会跟着操作按钮,页面底部会有操作按钮,页面带导航条。以及如果我们想组件功能更强,需要支持的情况更多,可以支持某个模块自定义展示。这个时候需要在原来的基础上进行功能扩展.
详情组件已开发好了,新增功能只需要在原来的基础上新增代码逻辑即可
/**
* @description 公共业务组件-详情
*/
import React, { useState } from 'react';
......
const CommonDetailBase = ({ ...props }) => {
const { dataList, affixTabs } = props;
/** @name 当前所在导航index值 */
const [afffixIndex, setAfffixIndex] = useState(props.afffixIndex);
/** @name 是否展示导航 */
const hasAffixTabs = !!affixTabs;
/**
* 快速定位方法
* @param {string} id 定位到的id值
* @return {void} 无
*/
const fastGo = id => {
let element = document.getElementById(id);
let view = document.querySelector('.view');
view.scrollTo({
top: element.offsetTop - 90,
});
};
/**
* 右侧锚点导航-切换
* @param {Object} item 选择的导航标签
* @return {void} 无
*/
const tabChange = item => {
setAfffixIndex(item.key);
fastGo(item.key);
};
/**
* row类页面内容回显
* @param {Object} data 展示内容对象
* @return {Element} 展示内容
*/
const dataRowContent = data => {
const list = data.list ? data.list : [];
return (
<>
{list.map((rowItem, rowIndex) => {
return (
<Col span={12} key={rowIndex}>
<Card size='small'>
{rowItem.children ? (
<>{rowItem.children}</>
) : (
<div>
{rowItem.name}:{rowItem.value}
{rowItem.colBtnList &&
rowItem.colBtnList.map((colBtnItem, colBtnIndex) => {
return (
<Button className='ml10' type='primary' onClick={() => colBtnItem.colBtnCallback(colBtnItem, rowItem)} key={colBtnIndex}>
{colBtnItem.name}
</Button>
);
})}
</div>
)}
</Card>
</Col>
);
})}
</>
);
};
/**
* Table类页面内容回显
* @param {Object} item 展示内容对象
* @return {Node} 展示内容
*/
const dataTableContent = item => {
let list = item.list ? item.list : [];
return <Table dataSource={list} columns={item.columns} rowKey={record => record.id} pagination={false} size='small' />;
};
return (
<div className={style['detail-base']}>
<div className='view-content' id='view'>
{dataList.map(item => {
return (
<Card type='inner' title={item.name} id={item.key} key={item.key} className='mb20'>
{item.children ? (
<>{item.children}</>
) : (
<>
{item.contentType === 'row' ? <Row gutter={[12, 12]}>{dataRowContent(item)}</Row> : null}
{item.contentType === 'table' ? dataTableContent(item) : null}
</>
)}
{item.moduleBottomList &&
item.moduleBottomList.map((moduleBtnItem, moduleBtnIndex) => {
return (
<Button className='mr10 mt20' type='primary' onClick={() => moduleBtnItem.moduleBtnCallback(moduleBtnItem, item)} key={moduleBtnIndex}>
{moduleBtnItem.name}
</Button>
);
})}
</Card>
);
})}
{/* 右侧锚点导航 */}
{hasAffixTabs ? (
<Affix offsetTop={120} className='sider-affix'>
<ul className='affix'>
{affixTabs.map((item, index) => (
<li key={index}>
<div className={classnames('affix-item', { current: afffixIndex === item.key })} onClick={() => tabChange(item)}>
{item.name}
</div>
</li>
))}
</ul>
</Affix>
) : null}
</div>
</div>
);
};
CommonDetailBase.propTypes = {
dataList: PropTypes.array, // 页面展示数组对象
affixTabs: PropTypes.array, // 导航数组对象
afffixIndex: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), // 导航默认定位
};
CommonDetailBase.defaultProps = {
dataList: [],
};
export default CommonDetailBase;
/**
* @description 详情页
*/
import React, { useState, useEffect } from 'react';
......
import { CommonDetailBase } from '@/bundleComponents';
const DetailBase = () => {
/** @name 页面导航 */
const affixTabs = [];
......
// 列表数据重组
dataListInit.map(item => {
......
// 设置导航条数据
affixTabs.push(item);
});
......
/** @name 详情组件配置项 */
const detailConfig = {
afffixIndex: 'userInfo',
affixTabs: affixTabs,
dataList: dataList,
};
return (
<div>
<CommonDetailBase {...detailConfig} />
</div>
);
};
export default DetailBase;
/**
* @description 详情页
*/
import React, { useState, useEffect } from 'react';
......
const DetailBase = () => {
......
let dataListInit = [
......
{
key: 'postInfo',
name: '快递信息',
},
......
];
......
const initData = () => {
let list = _.cloneDeep(dataListInit);
list.map(item => {
......
// =>true: 快递信息 表格项处理
if (item.key === 'postInfo') {
item.moduleBottomList = [
{
name: '快递详情',
moduleBtnCallback: (_, data) => moduleBottomCallback(data, res),
},
{
name: '快递详情2',
moduleBtnCallback: (_, data) => moduleBottomCallback(data, res),
},
];
}
})
}
......
};
export default DetailBase;
/**
* @description 详情页
*/
import React, { useState, useEffect } from 'react';
......
const DetailBase = () => {
......
const getUserData = data => {
let list = [
......
{
name: '收货地址',
value: data.address,
colBtnList: [
{
name: '地址详情',
colBtnCallback: () => {
window.open('https://juejin.cn/', '_blank');
},
},
],
},
.......
];
return list;
};
......
};
export default DetailBase;
将需要自定义展示的模块对象的children值设置为需要展示的内容即可
/**
* @description 详情页
*/
import React, { useState, useEffect } from 'react';
......
const DetailBase = () => {
......
/**
* 支付模块展示
* @param {Object} dafa 展示的数据对象
*/
const getPayInfo = data => {
return (
<Row gutter={[8, 8]}>
{data.list.map((item, index) => {
return (
<Col span={24} key={index}>
{item.name}:{item.value}
</Col>
);
})}
</Row>
);
};
......
const initData = () => {
......
let list = _.cloneDeep(dataListInit);
list.map(item => {
......
// =>true: 支付信息 自定义展示
if (item.key === 'payInfo') {
item.children = getPayInfo(item);
}
});
setDataList(list);
};
......
};
export default DetailBase;
将需要自定义展示的模块下的数据项对象的children值设置为需要展示的内容即可
/**
* @description 详情页
*/
import React, { useState, useEffect } from 'react';
......
const DetailBase = () => {
......
/**
* 图片类型展示
* @param {Object} data 展示的数据对象
*/
const getImageView = data => {
return (
<>
头像:<Button type="link">编辑</Button>
<Row gutter={(12, 12)}>
<Col span={4}>
<img style={{ width: '80px', height: '80px', display: 'block' }} src={data.headImage} />
</Col>
</Row>
</>
);
};
......
const getUserData = data => {
let list = [
......
{
name: '头像',
children: getImageView(data),
},
];
return list;
};
......
};
export default DetailBase;
以上,一个功能相对全面的详情页组件就完成了。
/**
* @description 公共业务组件-详情
*/
import React, { useState } from 'react';
import PropTypes from 'prop-types';
import classnames from 'classnames';
import { Button, Card, Row, Col, Table, Affix } from 'antd';
import style from './style';
const CommonDetailBase = ({ ...props }) => {
const { dataList, affixTabs } = props;
/** @name 当前所在导航index值 */
const [afffixIndex, setAfffixIndex] = useState(props.afffixIndex);
/** @name 是否展示导航 */
const hasAffixTabs = !!affixTabs;
/**
* 快速定位方法
* @param {string} id 定位到的id值
* @return {void} 无
*/
const fastGo = id => {
let element = document.getElementById(id);
let view = document.querySelector('.view');
view.scrollTo({
top: element.offsetTop - 90,
});
};
/**
* 右侧锚点导航-切换
* @param {Object} item 选择的导航标签
* @return {void} 无
*/
const tabChange = item => {
setAfffixIndex(item.key);
fastGo(item.key);
};
/**
* row类页面内容回显
* @param {Object} data 展示内容对象
* @return {Element} 展示内容
*/
const dataRowContent = data => {
const list = data.list ? data.list : [];
return (
<>
{list.map((rowItem, rowIndex) => {
return (
<Col span={12} key={rowIndex}>
<Card size='small'>
{rowItem.children ? (
<>{rowItem.children}</>
) : (
<div>
{rowItem.name}:{rowItem.value}
{rowItem.colBtnList &&
rowItem.colBtnList.map((colBtnItem, colBtnIndex) => {
return (
<Button className='ml10' type='primary' onClick={() => colBtnItem.colBtnCallback(colBtnItem, rowItem)} key={colBtnIndex}>
{colBtnItem.name}
</Button>
);
})}
</div>
)}
</Card>
</Col>
);
})}
</>
);
};
/**
* Table类页面内容回显
* @param {Object} item 展示内容对象
* @return {Node} 展示内容
*/
const dataTableContent = item => {
let list = item.list ? item.list : [];
return <Table dataSource={list} columns={item.columns} rowKey={record => record.id} pagination={false} size='small' />;
};
return (
<div className={style['detail-base']}>
<div className='view-content' id='view'>
{dataList.map(item => {
return (
<Card type='inner' title={item.name} id={item.key} key={item.key} className='mb20'>
{item.children ? (
<>{item.children}</>
) : (
<>
{item.contentType === 'row' ? <Row gutter={[12, 12]}>{dataRowContent(item)}</Row> : null}
{item.contentType === 'table' ? dataTableContent(item) : null}
</>
)}
{item.moduleBottomList &&
item.moduleBottomList.map((moduleBtnItem, moduleBtnIndex) => {
return (
<Button className='mr10 mt20' type='primary' onClick={() => moduleBtnItem.moduleBtnCallback(moduleBtnItem, item)} key={moduleBtnIndex}>
{moduleBtnItem.name}
</Button>
);
})}
</Card>
);
})}
{/* 右侧锚点导航 */}
{hasAffixTabs ? (
<Affix offsetTop={120} className='sider-affix'>
<ul className='affix'>
{affixTabs.map((item, index) => (
<li key={index}>
<div className={classnames('affix-item', { current: afffixIndex === item.key })} onClick={() => tabChange(item)}>
{item.name}
</div>
</li>
))}
</ul>
</Affix>
) : null}
</div>
</div>
);
};
CommonDetailBase.propTypes = {
dataList: PropTypes.array, // 页面展示数组对象
affixTabs: PropTypes.array, // 导航数组对象
afffixIndex: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), // 导航默认定位
};
CommonDetailBase.defaultProps = {
dataList: [],
};
export default CommonDetailBase;
组件样式
:local(.detail-base) {
position: relative;
padding-left: 20px;
background: #fff;
.view-content {
padding-top: 20px;
padding-right: 120px;
padding-bottom: 80px;
}
.sider-affix {
position: fixed;
top: 50px;
right: 10px;
.affix {
padding-left: 0;
margin: 16px 0;
font-size: 12px;
list-style: none;
border-left: 1px solid #f0f0f0;
li {
padding-left: 0;
margin-left: 0;
line-height: 2;
list-style: none;
}
.affix-item {
display: block;
width: 110px;
padding-left: 16px;
margin-left: -1px;
overflow: hidden;
font-size: 16px;
color: rgba(0, 0, 0, 0.85);
text-overflow: ellipsis;
white-space: nowrap;
cursor: pointer;
border-left: 1px solid transparent;
&.current {
color: #1890ff;
border-color: #1890ff;
}
}
}
}
}

对后台系统代码简洁之路,仍在探索中,后续想实现列表页的操作表单项的设计,这样后台系统的基础的页面能快速完成搭建。
当然了,更好的方式是搭建低代码平台,但是现有的开发精力并不能支撑完成这种复杂的开发。所以先从基础出发,逐步升级自己的技能。
我有一个模型:classItem项目有一个属性“商店”基于存储的值,我希望Item对象对特定方法具有不同的行为。Rails中是否有针对此的通用设计模式?如果方法中没有大的if-else语句,这是如何干净利落地完成的? 最佳答案 通常通过Single-TableInheritance. 关于ruby-on-rails-Rails-子类化模型的设计模式是什么?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.co
如何在buildr项目中使用Ruby?我在很多不同的项目中使用过Ruby、JRuby、Java和Clojure。我目前正在使用我的标准Ruby开发一个模拟应用程序,我想尝试使用Clojure后端(我确实喜欢功能代码)以及JRubygui和测试套件。我还可以看到在未来的不同项目中使用Scala作为后端。我想我要为我的项目尝试一下buildr(http://buildr.apache.org/),但我注意到buildr似乎没有设置为在项目中使用JRuby代码本身!这看起来有点傻,因为该工具旨在统一通用的JVM语言并且是在ruby中构建的。除了将输出的jar包含在一个独特的、仅限ruby
在rails源中:https://github.com/rails/rails/blob/master/activesupport/lib/active_support/lazy_load_hooks.rb可以看到以下内容@load_hooks=Hash.new{|h,k|h[k]=[]}在IRB中,它只是初始化一个空哈希。和做有什么区别@load_hooks=Hash.new 最佳答案 查看rubydocumentationforHashnew→new_hashclicktotogglesourcenew(obj)→new_has
我将应用程序升级到Rails4,一切正常。我可以登录并转到我的编辑页面。也更新了观点。使用标准View时,用户会更新。但是当我添加例如字段:name时,它不会在表单中更新。使用devise3.1.1和gem'protected_attributes'我需要在设备或数据库上运行某种更新命令吗?我也搜索过这个地方,找到了许多不同的解决方案,但没有一个会更新我的用户字段。我没有添加任何自定义字段。 最佳答案 如果您想允许额外的参数,您可以在ApplicationController中使用beforefilter,因为Rails4将参数
我的主要目标是能够完全理解我正在使用的库/gem。我尝试在Github上从头到尾阅读源代码,但这真的很难。我认为更有趣、更温和的踏脚石就是在使用时阅读每个库/gem方法的源代码。例如,我想知道RubyonRails中的redirect_to方法是如何工作的:如何查找redirect_to方法的源代码?我知道在pry中我可以执行类似show-methodmethod的操作,但我如何才能对Rails框架中的方法执行此操作?您对我如何更好地理解Gem及其API有什么建议吗?仅仅阅读源代码似乎真的很难,尤其是对于框架。谢谢! 最佳答案 Ru
我的假设是moduleAmoduleBendend和moduleA::Bend是一样的。我能够从thisblog找到解决方案,thisSOthread和andthisSOthread.为什么以及什么时候应该更喜欢紧凑语法A::B而不是另一个,因为它显然有一个缺点?我有一种直觉,它可能与性能有关,因为在更多命名空间中查找常量需要更多计算。但是我无法通过对普通类进行基准测试来验证这一点。 最佳答案 这两种写作方法经常被混淆。首先要说的是,据我所知,没有可衡量的性能差异。(在下面的书面示例中不断查找)最明显的区别,可能也是最著名的,是你的
几个月前,我读了一篇关于rubygem的博客文章,它可以通过阅读代码本身来确定编程语言。对于我的生活,我不记得博客或gem的名称。谷歌搜索“ruby编程语言猜测”及其变体也无济于事。有人碰巧知道相关gem的名称吗? 最佳答案 是这个吗:http://github.com/chrislo/sourceclassifier/tree/master 关于ruby-寻找通过阅读代码确定编程语言的rubygem?,我们在StackOverflow上找到一个类似的问题:
我目前正在使用以下方法获取页面的源代码:Net::HTTP.get(URI.parse(page.url))我还想获取HTTP状态,而无需发出第二个请求。有没有办法用另一种方法做到这一点?我一直在查看文档,但似乎找不到我要找的东西。 最佳答案 在我看来,除非您需要一些真正的低级访问或控制,否则最好使用Ruby的内置Open::URI模块:require'open-uri'io=open('http://www.example.org/')#=>#body=io.read[0,50]#=>"["200","OK"]io.base_ur
前言作为一名程序员,自己的本质工作就是做程序开发,那么程序开发的时候最直接的体现就是代码,检验一个程序员技术水平的一个核心环节就是开发时候的代码能力。众所周知,程序开发的水平提升是一个循序渐进的过程,每一位程序员都是从“菜鸟”变成“大神”的,所以程序员在程序开发过程中的代码能力也是根据平时开发中的业务实践来积累和提升的。提高代码能力核心要素程序员要想提高自身代码能力,尤其是新晋程序员的代码能力有很大的提升空间的时候,需要针对性的去提高自己的代码能力。提高代码能力其实有几个比较关键的点,只要把握住这些方面,就能很好的、快速的提高自己的一部分代码能力。1、多去阅读开源项目,如有机会可以亲自参与开源
在编写Ruby(客户端脚本)时,我看到了三种构建更长字符串的方法,包括行尾,所有这些对我来说“闻起来”有点难看。有没有更干净、更好的方法?变量递增。ifrender_quote?quote="NowthatthereistheTec-9,acrappyspraygunfromSouthMiami."quote+="ThisgunisadvertisedasthemostpopularguninAmericancrime.Doyoubelievethatshit?"quote+="Itactuallysaysthatinthelittlebookthatcomeswithit:themo