过一段时间后的批注:看了慕课网老师的react课程,这篇文章我推荐的链接还是值得看,但我对react的解释就不用看啦,因为部分理解还是不到位,而且没有对中间件的解释,但我是不会删除这个宝藏的,嘻嘻。
阮一峰老师的文章链接:http://www.ruanyifeng.com/blog/2016/09/redux_tutorial_part_one_basic_usages.html
Sia-UI的github链接:https://github.com/NebulousLabs/Sia-UI
博客写完也差不多记住了,记住步骤了就理解了,没记住就看不懂,这是我对react-redux的深刻理解,哈哈。
有几个关键名词需要记一下,定义是我理解的写法⊙_⊙:
ui组件:只负责美,页面的展示;这部分就是展示数据而已而已啦。
容器组件:只负责脑子,页面的逻辑,如果二者有重合的话,要井水不犯河水,细分细分细分成更小的ui组件和容器组件。这部分就是对数据进行处理,调起action里的事件。
action:组件里所有事件的定义都写在这里,具体就是ui组件里面要调用的事件名字,为什么这样,因为代码就要规规矩矩,整整齐齐,这样才能透出满屏的大佬范;这部分就是让你往东,你不能往西的作用。
reducer:根据用户触发的action具体事件在自己内部找到具体处理方法,更新一些状态的值,再把这些值传给容器组件;这部分就是让你往东,东到具体哪个位置,乱东要挨打的哟。
总体思路就是这样的:
1.先在ui组件里面绑定事件,等待触发
2.用户触发后,关键点,容器组件将触发的事件名匹配mapDispatchToProps这个参数定义的相应事件,触发action
3.action收到通知后,找到事件对应的type类型
4.然后由reducer判断对应的type类型,设置对应的状态值
5.然后由容器组件的mapStateToProps参数更新到对应的view上面
反正我是这么理解的◑﹏◐◑﹏◐◑﹏◐,如果理解错了,下边的文字也是可以看一看的。
一、附一张项目代码结构图
二、file组件目录结构解释
assets/放各种image文件
css/放各种css文件
index.html -- 1.react-root:定义根节点
line15--用法:<div id="react-root"></div>
<!DOCTYPE html>
<html>
<head>
<title>Files</title>
<link rel='stylesheet' href='../../css/fonts.css'>
<link rel='stylesheet' href='../../css/font-awesome.min.css'>
<link rel='stylesheet' href='../../css/plugin-standard.css'>
<link rel='stylesheet' href='../../css/pure-min.css'>
<link rel='stylesheet' href='css/files.css'>
</head>
<body>
<!-- React root -->
<div id="react-root"></div>
<script>
{
const scripts = [];
// Dynamically insert the bundled app script in the renderer process
const port = process.env.PORT || 1212;
if (process.env.HOT) {
scripts.push('http://localhost:' + port + '/dist/plugins/Files.dev.js')
} else {
scripts.push('../../dist/plugins/Files.prod.js')
scripts.push('../../dist/commons.prod.js')
}
document.write(
scripts
.map(script => `<script defer src="${script}"><\/script>`)
.join('')
);
}
</script>
</body>
</html>
划重点----------------------------------------------------------------------------------------
js/index.js -- 1.createStore from redux:创建store对象,store对象用来保存状态数据
line17--用法:const store = createStore(rootReducer, applyMiddleware(sagaMiddleware))
store.getState('xx');store.dispatch('loadData')。手动获取状态,触发事件,很方便。
(1)rootReducer:用户触发一个action,store对象需要对相应的数据进行state更新,然后反映到view组件,这种state的变化是通过reducer处理的。
(2)sagaMiddleware为中间件这个处理异步请求的,没有异步请求就不用,这个不懂,省略。
2.Provider from react-redux:给容器组件传递state对象,包在最外层,这样App组件内的所有组件都能拿到state对象
line21--用法:<Provider store={store}><App /></Provider>
3.ReactDOM form react-dom:在根节点渲染react组件
line25--用法:ReactDOM.render(rootElement, document.getElementById('react-root'))
import React from 'react'
import ReactDOM from 'react-dom'
import createSagaMiddleware from 'redux-saga'
import { createStore, applyMiddleware } from 'redux'
import { Provider } from 'react-redux'
import rootReducer from './reducers/index.js'
import rootSaga from './sagas/index.js'
import App from './containers/app.js'
import { fetchData } from './actions/files.js'
// If dev enable window reload
if (process.env.NODE_ENV === 'development') {
require('electron-css-reload')()
}
const sagaMiddleware = createSagaMiddleware()
const store = createStore(rootReducer, applyMiddleware(sagaMiddleware))
sagaMiddleware.run(rootSaga)
const rootElement = (
<Provider store={store}>
<App />
</Provider>
)
ReactDOM.render(rootElement, document.getElementById('react-root'))
// update state when plugin is focused
window.onfocus = () => {
store.dispatch(fetchData())
}
状态管理解释----------------------------------------------------------------------------------------
js/reducers/index.js -- 1.combineReducers from redux:将多个状态处理合并为一个大的,对于大项目需要各司其职,不然很容易乱
line8-用法:const rootReducer = combineReducers({wallet,files,deletedialog,renamedialog,allowancedialog})
import { combineReducers } from 'redux'
import wallet from './wallet.js'
import files from './files.js'
import deletedialog from './deletedialog.js'
import renamedialog from './renamedialog.js'
import allowancedialog from './allowancedialog.js'
const rootReducer = combineReducers({
wallet,
files,
deletedialog,
renamedialog,
allowancedialog
})
export default rootReducer
js/reducers/allowancedialog.js -- 以allowancedialog为例解释具体到某一个reducer的用法
reducer接收state和action两个参数,state为默认的状态键值对,reducer根据action触发的事件类型设置相应的state状态;action由ui组件中相应的事件触发
import { Map } from 'immutable'
import * as constants from '../constants/files.js'
const initialState = Map({
storageEstimate: '0 B',
feeEstimate: 0,
confirming: false,
confirmationAllowance: '0'
})
export default function allowancedialogReduceR (state = initialState, action) {
switch (action.type) {
case constants.SHOW_ALLOWANCE_CONFIRMATION:
return state
.set('confirming', true)
.set('confirmationAllowance', action.allowance)
case constants.HIDE_ALLOWANCE_CONFIRMATION:
return state.set('confirming', false)
case constants.CLOSE_ALLOWANCE_DIALOG:
return state.set('confirming', false)
case constants.SET_FEE_ESTIMATE:
return state.set('feeEstimate', action.estimate)
case constants.SET_STORAGE_ESTIMATE:
return state.set('storageEstimate', action.estimate)
default:
return state
}
}
js/actions/files.js -- action中每个事件的type类型是必需的,其他参数根据事件需要的参数自定义,引用为action.参数名
import * as constants from '../constants/files.js'
export const showAllowanceConfirmation = allowance => ({
type: constants.SHOW_ALLOWANCE_CONFIRMATION,
allowance
})
export const hideAllowanceConfirmation = () => ({
type: constants.HIDE_ALLOWANCE_CONFIRMATION
})
export const closeAllowanceDialog = () => ({
type: constants.CLOSE_ALLOWANCE_DIALOG
})
export const setStorageEstimate = estimate => ({
type: constants.SET_STORAGE_ESTIMATE,
estimate
})
export const setFeeEstimate = estimate => ({
type: constants.SET_FEE_ESTIMATE,
estimate
})
js/components/allowancedialog.js -- 1.ui组件,负责与用户的交互,传递用户点击的事件和事件参数给reducer
2.PropTypes from prop-types:规定参数类型,这些参数的值是action文件里面自定义的其他参数的值,line106
import PropTypes from 'prop-types'
import React from 'react'
import UnlockWarning from './unlockwarning.js'
import ConfirmationDialog from './allowanceconfirmation.js'
import BigNumber from 'bignumber.js'
const AllowanceDialog = ({
confirming,
confirmationAllowance,
unlocked,
synced,
feeEstimate,
storageEstimate,
actions
}) => {
const onCancelClick = () => actions.closeAllowanceDialog()
const onConfirmationCancel = () => actions.hideAllowanceConfirmation()
const onConfirmClick = () => actions.setAllowance(confirmationAllowance)
const onAcceptClick = e => {
e.preventDefault()
actions.showAllowanceConfirmation(e.target.allowance.value)
}
const onAllowanceChange = e => actions.getStorageEstimate(e.target.value)
const dialogContents = confirming ? (
<ConfirmationDialog
allowance={confirmationAllowance}
onConfirmClick={onConfirmClick}
onCancelClick={onConfirmationCancel}
/>
) : (
<div className='allowance-dialog'>
<h3> Buy storage on the Sia Decentralized Network</h3>
<div className='allowance-message'>
<p>
You need to allocate funds to upload and download on Sia. Your
allowance remains locked for 3 months. Unspent funds are then
refunded*. You can increase your allowance at any time.
</p>
<p>
Your storage allowance automatically refills every 6 weeks. Your
computer must be online with your wallet unlocked to complete the
refill. If Sia fails to refill the allowance by the end of the lock-in
period, your data may be lost.
</p>
<p className='footnote'>
*contract fees are non-refundable. They will be subtracted from the
allowance that you set.
</p>
</div>
<form onSubmit={onAcceptClick}>
<div className='allowance-input'>
<label>
Allowance:
<input
type='number'
name='allowance'
defaultValue='5000'
onFocus={onAllowanceChange}
onChange={onAllowanceChange}
required
autoFocus
className='allowance-amount'
/>
</label>
<span> SC</span>
</div>
<div className='allowance-buttons'>
<button type='submit' className='allowance-button-accept'>
Accept
</button>
<button
type='button'
onClick={onCancelClick}
className='allowance-button-cancel'
>
Cancel
</button>
</div>
<table className='estimates'>
<tr>
<td className='estimate-label'>Estimated Fees</td>
<td className='estimate-content'>
{new BigNumber(feeEstimate).round(2).toString()} SC
</td>
</tr>
<tr>
<td className='estimate-label'>Estimated Storage</td>
<td className='estimate-content'>{storageEstimate}</td>
</tr>
</table>
</form>
</div>
)
return (
<div className='modal'>
{unlocked && synced ? (
dialogContents
) : (
<UnlockWarning onClick={onCancelClick} />
)}
</div>
)
}
AllowanceDialog.propTypes = {
confirmationAllowance: PropTypes.string.isRequired,
confirming: PropTypes.bool.isRequired,
unlocked: PropTypes.bool.isRequired,
synced: PropTypes.bool.isRequired,
feeEstimate: PropTypes.number.isRequired,
storageEstimate: PropTypes.string.isRequired
}
export default AllowanceDialog
js/containers/allowancedialog.js -- 1.容器组件,负责处理用户交互的逻辑
2.connect from react-redux:将ui组件和容器组件连接起来,接受两个参数:mapStateToProps和mapDispatchToProps。前者负责展示在页面上,后者负责处理用户的响应事件,这个connect是生成与ui组件联系的容器组件,最后的括号里传入对应的ui组件。
import AllowanceDialogView from '../components/allowancedialog.js'
import { connect } from 'react-redux'
import { bindActionCreators } from 'redux'
import {
showAllowanceConfirmation,
hideAllowanceConfirmation,
closeAllowanceDialog,
setAllowance,
setFeeEstimate,
getStorageEstimate
} from '../actions/files.js'
const mapStateToProps = state => ({
unlocked: state.wallet.get('unlocked'),
synced: state.wallet.get('synced'),
storageEstimate: state.allowancedialog.get('storageEstimate'),
feeEstimate: state.allowancedialog.get('feeEstimate'),
confirmationAllowance: state.allowancedialog.get('confirmationAllowance'),
confirming: state.allowancedialog.get('confirming')
})
const mapDispatchToProps = dispatch => ({
actions: bindActionCreators(
{
getStorageEstimate,
setFeeEstimate,
showAllowanceConfirmation,
setAllowance,
hideAllowanceConfirmation,
closeAllowanceDialog
},
dispatch
)
})
const AllowanceDialog = connect(mapStateToProps, mapDispatchToProps)(
AllowanceDialogView
)
export default AllowanceDialog