前言
如果從來不了解React先看前篇React入門 (1)—使用指南(包括ES5和ES6對比)。
本文為了能將前篇學到的react知識學以致用,做了一個類似微博展示列表的demo。使用的是ES6+React+JSX+Webpack+Babel+NPM
設計思路
圖為截取的微博展示列表,我這么來划分組件:
花括號括起來的是我要寫的幾個組件:
- ContentImg組件:帶圖片的微博里的圖片部分
- CommentForm組件:點擊評論后,彈出來的評論下拉框
- OneWB組件:一條微博,這條微博可能是純文字的,帶一張圖片的,帶多張圖片的,甚至是轉發的。
- ListWB組件:OneWB的集合,是一個頁面要展示的所有微博的集合。
最外層節點放在最上面,每個控件的數量通過后台給的數據控制,組件之間的關系用樹狀圖表示:
本文章只實現純文字,帶圖片的微博。帶視屏,轉發的可以用類似的方法實現,就不贅述了。后端數據的結構設計為:
var dataList=[
{
headUrl:'http://img1.gtimg.com/tech/pics/hv1/238/85/1736/112905313.jpg',
nickName:'Robin',
content:'拿快遞拿快遞3號小郵局爆倉啦',
NoCollect:132,
NoForward:202,
NoComment:142,
NoPointGreat:423,
contentImgUrls:[
"http://img1.gtimg.com/tech/pics/hv1/238/85/1736/112905313.jpg",
"http://img1.gtimg.com/tech/pics/hv1/238/85/1736/112905313.jpg"
]
},
{
//內容同上
}
];
- 必填
headUrl:別人的頭像鏈接,nickName:別人的昵稱,content:微博文字內容,NoCollect:收藏數,NoForward:轉發數,NoComment:評論數,NoPointGreat:點贊數。 - 非必填
contentImgUrls:微博圖片內容,可以為空或不存在。
圖中組件的數量是根據后端給的數據來決定的,dataList的元素個數代表OneWB的個數,contentImgUrls的元素個數決定了微博帶不帶圖片,以及展示幾個。
搭建環境
使用webpack+npm。
我的工程目錄結構如圖:
webpack以commonjs的形式來書寫腳本,是現在很火的模塊加載器+打包工具。使用方法:
1.創建package.json文件
npm init
參數可以不填,回車代替。
執行完會生成package.json文件
2.在終端安裝你需要的插件
npm install 插件名 --save-dev.
('--save-dev' 一般在開發者開發項目的時候用,這個指令會把安裝的包依賴寫入package.json的devDependencies字段中。如果命令去掉'-dev',則會記錄到dependencies字段中)。
需要安裝這么些個:
npm install -save-dev webpack
npm install -save-dev react
npm install -save-dev react-dom
npm install --save-dev babel-core
npm install --save-dev babel-loader
npm install --save-dev babel-preset-es2015
,babel6才需要裝
npm install -save-dev babel-preset-react
,babel6才需要裝
如果用的別人的工程,已存在package.json文件且內容完整,那么直接npm install不需要手動安裝了。
3.創建webpack.config.js,並配置
根據webpack.config.js文件來決定webpack要做哪些動作。
//webpack.config.js 文件內容
var path = require('path');
module.exports = {
entry: {
'index': './index.js' //key只是個名字,可以自由改
},
output: {
path: './build',
filename: 'entry.js',//也可以動態生成文件名 filename:'[name].js',將根據entry中的key生成名字
},
module: {
loaders: [{
test: /\.jsx?$/,
loader: 'babel',
/* babel6 才需要配置這個,presets里面兩個預編譯插件,前一個用於編譯es6,后一個用於編譯react。按需配置。這個工程都需要。
query:{
presets: ['es2015','react']
}*/
}]
}
};
這段代碼主要告訴了webpack:
- 哪個文件需要打包(entry字段),打包之后生成的新文件存到哪個路徑(output中的path)、新文件叫什么名字(output中的filename);
- 要使用哪些加載器(module.loaders)。這里要使用babel來編譯jsx和es6的代碼。
總得來說,webpack從entry拿到目標文件,通過loaders進行編譯,從output輸出,其他功能由plugins引入。
注:index.js:負責渲染組件到頁面上。相當於一個總的出口。因為會自動加載依賴關系,所以webpack.config.js文件只需要配置這一文件即可。
另外,這個工程比較簡單,只需配置一個js文件。
- 如果要打包多個js文件,這么配置:
entry: {
'file1': './index.js', //key只是個名字,可以自由改
'file2': './index2.js'
}
- 如果需要打包css文件,或解析less文件,需要配置ExtractTextPlugin。詳情看官方文檔
4. 在終端執行命令:webpack
會在build文件夾下生成編譯后(未壓縮)的js文件,entry.js。如果編譯錯誤會在命令行提示錯誤原因,entry.js為上一版本的內容。
你也可以使用其他命令,方便開發:
webpack -w
監聽你的代碼修改,實施打包生成entry.js, 一般都直接使用這個命令。
webpack -p
打包js文件,並壓縮。
5. 開始編碼
完成以上四步,你就可以一邊編寫代碼,用require(es6用import)來加載依賴的模塊,一邊在瀏覽器查看效果啦。並且如果以后有新的項目,直接拷貝package.json和webpack.config.js ,秒搭建環境。
編碼階段
需要用的react特性有:事件,state,props。依然可以參見上一篇的詳細介紹。
- 每個組件文件都需要引入react
import React from 'react';
,以及依賴的其他組件。
index.js還需要引入reactdomimport ReactDOM from 'react-dom';
,因為渲染頁面的方法render()是reactdom的。 - 用class定義組件,不要忘記使用
module.exports = ListWB;
開放組件給其他文件引用。
index.html
只需引入一個文件,編譯后的文件entry.js。div#place為react組件的父容器。是不是看起來好像什么都沒有呢?react中,通過js來插入組件,我們看index.js文件
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>weibo-react</title>
<link rel="stylesheet" href="css/style.css">
</head>
<body>
<div id="place">
<!-- 此處插入react插件 -->
</div>
<!-- dom定義 -->
<script src="build/entry.js"></script>
</body>
</html>
index.js - js入口文件
使用自定義的react組件,渲染頁面。
import React from 'react';
import ReactDOM from 'react-dom';
import ListWB from './components/list-wb.jsx';//微博列表組件
var dataList=[{},{}];//這里存儲的是后端傳來的數據。包括頭像,內容,點贊數等等
ReactDOM.render(<ListWB data={dataList} />,
document.getElementById('place')
);
ListWB是微博列表組件,通過自定義屬性data傳遞后端數據,並set到子組件中,展示到頁面上。
注意import ListWB from './components/list-wb.jsx';
一定要記得引入自定義組件模塊。
list-wb.jsx - 微博列表組件
import React from 'react';
import OneWB from './one-wb.jsx';//標簽的名字根據這個變量名來決定
class ListWB extends React.Component{
render() {
// 遍歷后端給的數據,並且插入
var oneWBNodes = this.props.data.map(function(aWB,index){
return <OneWB oneData={aWB} key={index}></OneWB>;
});
return <div>
{oneWBNodes}
</div>;
}
}
module.exports = ListWB;
筆記:
- map方法會遍歷數組中的所有元素,並執行匿名函數,當前元素aWB作為匿名函數第一個實參,當前元素索引index作為第二個實參。this.props.data存儲的后端數據list,每個元素執行完匿名函數return的值會連接(相加)起來。所以oneWBNodes得到是OneWB的list。
- 注意有一個自定義屬性key。對於循環出的每個子組件,如果不定義key會報警。
react中data-reactid的取名方式:
左圖為沒有數組的“dom樹”取名,右圖為有數組的“dom樹”取名。當圖的.0.1是數組時,id會按照右圖的虛線框那么取。
react組件中,每個標簽都會有一個唯一的編號data-reactid,如:data-reactid=".0.$0a.0.1.0.1.1"。這里的key是data-reactid的最后一個小數點后面的值。所以key只需要在自己兄弟節點內唯一即可。
如果不按照要求,讓多個子元素的key相同,那么只會識別展示第一個子元素。
one-wb.jsx - 單個微博組件
該組件代表單條微博。要處理的有:
- 屬性
存儲了后端傳過來的數據。正確分配給子組件或者set到自己的原生屬性中。 - 事件
觸發后使用react的state來自動渲染頁面。點擊“評論”展開評論塊,點擊其他(如“轉發”,“贊”)收起評論。這里通過給react的state增加一個字段isComment來控制評論區塊的展現。點擊“評論”,isComment設為true,展示評論區塊;否則isComment設為false。 - JSX中的if-else
JSX中是沒有if-else的(因為jsx只是個語法糖,最終還是要翻譯成原生js。比如:
<div id={if (condition) { 'msg' }}>Hello World!</div>
編譯后是:
React.createElement("div", {id: if (condition) { 'msg' }}, "Hello World!");
然而我們還是有很多場景需要根據條件判斷某些組件要不要展示。
我們用這幾種方式來實現:
- 三元操作符
React.render(<div id={condition ? 'msg' : ''}>Hello World!</div>, mountNode);
- 在jsx以外賦值給變量
var vid='';
if(condition){
vid='msg';
}
React.render(<div id={vid}>Hello World!</div>, mountNode);
- 立即執行的匿名函數
<div id={(()=>{
if(this.state.isComment){
return 4;
}
})()}>
</div>
匿名函數這種方式也同樣可以用在標簽中間 <div>{(()=>{//函數內容})()}</div>
我使用的是第二種方式,代碼還是更易維護一些。
這個文件的代碼:
import React from 'react';
import CommentForm from './comment-form.jsx';
import ContentImg from './content-img.jsx';
class OneWB extends React.Component{
constructor(props){
super(props);
this.state={
isComment:false,
};
}
render() {
var oneData=this.props.oneData;
var commentForm;
var contentImgs;
if(this.state.isComment) {
//控制評論框是否展現,因為是動態的,所以放在state而不是props
commentForm =<CommentForm data-my-head-img={oneData.headUrl}/>;
}
if(oneData.contentImgUrls){
//若后端給的數據中有圖片url,則展示
contentImgs = <ContentImg content-img-urls={oneData.contentImgUrls} />
}
return <div className="big-center" >
<div className="one-wb ">
<div className="clearfix">
<div className="ow-left">
<img className="nick-img" src={oneData.headUrl}/>
</div>
<div className="ow-right">
<div className="ow-nick row">
<span>{oneData.nickName}</span>
</div>
<div className="ow-content row">{oneData.content}</div>
{contentImgs}
</div>
</div>
<div className="ow-footer row ">
<ul className="clearfix" onClick={this.handlerForwardClick.bind(this)}>
<li className="li-side-border"><span>收藏</span> {oneData.NoCollect}</li>
<li className="li-side-border"><span>轉發</span> {oneData.NoForward}</li>
<li className="li-side-border"><span>評論</span> {oneData.NoComment}</li>
<li><span>贊</span> {oneData.NoPointGreat}</li>
</ul>
</div>
</div>
{commentForm}
</div>;
}
handlerForwardClick(event) {
if(event.target.innerText == '評論'){
this.setState({isComment:true});
}else{
this.setState({isComment:false});
}
}
}
module.exports = OneWB;
content-img.jsx - 微博圖片組件
因為子組件放在一個數組里,同樣需要注意配置key
import React from 'react';
class ContentImg extends React.Component{
render() {
var imgNodes=this.props['content-img-urls'].map(function(oneImg,index){
return <li key={index} ><img src={oneImg} alt="微博配圖" /></li>;
});
return <div className="row extra-content clearfix">
<ul>
{imgNodes}
</ul>
</div>
}
}
module.exports = ContentImg;
comment-form.jsx - 評論框組件
import React from 'react';
class CommentForm extends React.Component{
render() {
var imgUrl=this.props['data-my-head-img'];
return <div className="row comment-form clearfix" >
<div className="ow-left">
<img src={imgUrl} alt="頭像" className="little-head" />
</div>
<div className="ow-right" >
<textarea name="name" rows="8" cols="40" className="comment-box"></textarea>
<input className="comment-btn" type="submit" value="評論"/>
</div>
</div>;
}
}
module.exports = CommentForm;
效果展示
總結
看完這篇文章,相信能夠快速在工作當中實踐了吧 (≧▽≦)/ 。希望以后能出一些深刻的文章。