首页
统计
墙纸
留言
Search
1
PVE8优化
19 阅读
2
mysql创建数据库
12 阅读
3
jenkins根据分支、文件夹打包
12 阅读
4
vue-cli注册全局方法
7 阅读
5
开心的加班
7 阅读
web前端
Vue
CSS
javascript
React
那些年爬过过的坑
ES6
TypeScrippt
ES7
javascript图灵 - 总结
Node
面试总结
React-Native
Web优化
基础
AngularJS
拍摄
Flutter
Dart
Docker
Linux
mysql
PVE
登录
/
注册
Search
标签搜索
vue+elementui
Cicaba
累计撰写
146
篇文章
累计收到
13
条评论
首页
栏目
web前端
Vue
CSS
javascript
React
那些年爬过过的坑
ES6
TypeScrippt
ES7
javascript图灵 - 总结
Node
面试总结
React-Native
Web优化
基础
AngularJS
拍摄
Flutter
Dart
Docker
Linux
mysql
PVE
页面
统计
墙纸
留言
搜索到
13
篇与
的结果
2018-12-12
React 16 生命周期函数
1.constructor(props)react组件的构造函数在挂载之前被调用。在实现React.Component构造函数时,需要先在添加其他内容前,调用super(props),用来将父组件传来的props绑定到这个类中,使用this.props将会得到。官方建议不要在constructor引入任何具有副作用和订阅功能的代码,这些应当在componentDidMount()中写入。constructor中应当做些初始化的动作,如:初始化state,将事件处理函数绑定到类实例上,但也不要使用setState()。如果没有必要初始化state或绑定方法,则不需要构造constructor,或者把这个组件换成纯函数写法。当然也可以利用props初始化state,在之后修改state不会对props造成任何修改,但仍然建议大家提升状态到父组件中,或使用redux统一进行状态管理。constructor(props) { super(props); this.state = { color: props.initialColor }; }2.static getDerivedStateFromProps(nextProps, prevState)getDerivedStateFromProps在组件实例化后,和接受新的props后被调用。他返回一个对象来更新状态,或者返回null表示新的props不需要任何state的更新。如果是由于父组件的props更改,所带来的重新渲染,也会触发此方法。调用steState()不会触发getDerivedStateFromProps()。3. componentWillMount() / UNSAFE_componentWillMount()componentWillMount()将在react未来版本中被弃用。UNSAFE_componentWillMount()在组件挂载前被调用,在这个方法中调用setState()不会起作用,是由于他在render()前被调用。为了避免副作用和其他的订阅,官方都建议使用componentDidMount()代替。这个方法是用于在服务器渲染上的唯一方法。4.render()render()方法是必需的。当他被调用时,他将计算this.props和this.state,并返回以下一种类型:React元素。通过jsx创建,既可以是dom元素,也可以是用户自定义的组件。字符串或数字。他们将会以文本节点形式渲染到dom中。Portals。react 16版本中提出的新的解决方案,可以使组件脱离父组件层级直接挂载在DOM树的任何位置。null,什么也不渲染布尔值。也是什么都不渲染,通常后跟组件进行判断。当返回null,false,ReactDOM.findDOMNode(this)将会返回null,什么都不会渲染。render()方法必须是一个纯函数,他不应该改变state,也不能直接和浏览器进行交互,应该将事件放在其他生命周期函数中。 如果shouldComponentUpdate()返回false,render()不会被调用。Fragments你也可以在render()中使用数组,如:(不要忘记给每个数组元素添加key,防止出现警告)render() { return [ <li key="A">First item</li>, <li key="B">Second item</li>, <li key="C">Third item</li>, ]; } ```` 换一种写法,可以不写key(v16++)render() { return (<React.Fragment> <li>First item</li> <li>Second item</li> <li>Third item</li> </React.Fragment>);}**5.componentWillReceiveProps()**/ UNSAFE_componentWillReceiveProps(nextProps) 官方建议使用getDerivedStateFromProps函数代替componentWillReceiveProps()。当组件挂载后,接收到新的props后会被调用。如果需要更新state来响应props的更改,则可以进行this.props和nextProps的比较,并在此方法中使用this.setState()。 如果父组件会让这个组件重新渲染,即使props没有改变,也会调用这个方法。 react不会在组件初始化props时调用这个方法。调用this.setState也不会触发。 **6.shouldComponentUpdate(nextProps, nextState)** 调用shouldComponentUpdate使react知道,组件的输出是否受state和props的影响。默认每个状态的更改都会重新渲染,大多数情况下应该保持这个默认行为。 在渲染新的props或state前,shouldComponentUpdate会被调用。默认为true。这个方法不会在初始化时被调用,也不会在forceUpdate()时被调用。返回false不会阻止子组件在state更改时重新渲染。 如果shouldComponentUpdate()返回false,componentwillupdate,render和componentDidUpdate不会被调用。 在未来版本,shouldComponentUpdate()将会作为一个提示而不是严格的指令,返回false仍然可能导致组件的重新渲染。 官方并不建议在shouldComponentUpdate()中进行深度查询或使用JSON.stringify(),他效率非常低,并且损伤性能。 **7.UNSAFE_componentWillUpdate(nextProps, nextState)** 在渲染新的state或props时,UNSAFE_componentWillUpdate会被调用,将此作为在更新发生之前进行准备的机会。这个方法不会在初始化时被调用。 不能在这里使用this.setState(),也不能做会触发视图更新的操作。如果需要更新state或props,调用getDerivedStateFromProps。 **8.getSnapshotBeforeUpdate()** 在react render()后的输出被渲染到DOM之前被调用。它使您的组件能够在它们被潜在更改之前捕获当前值(如滚动位置)。这个生命周期返回的任何值都将作为参数传递给componentDidUpdate()。 **9.componentDidUpdate(prevProps, prevState, snapshot)** 在更新发生后立即调用componentDidUpdate()。此方法不用于初始渲染。当组件更新时,将此作为一个机会来操作DOM。只要您将当前的props与以前的props进行比较(例如,如果props没有改变,则可能不需要网络请求),这也是做网络请求的好地方。 如果组件实现getSnapshotBeforeUpdate()生命周期,则它返回的值将作为第三个“快照”参数传递给componentDidUpdate()。否则,这个参数是undefined。 **10.componentWillUnmount()** 在组件被卸载并销毁之前立即被调用。在此方法中执行任何必要的清理,例如使定时器无效,取消网络请求或清理在componentDidMount()中创建的任何监听。 **11.componentDidCatch(error, info)** 错误边界是React组件,可以在其子组件树中的任何位置捕获JavaScript错误,记录这些错误并显示回退UI,而不是崩溃的组件树。错误边界在渲染期间,生命周期方法以及整个树下的构造函数中捕获错误。 如果类组件定义了此生命周期方法,则它将成为错误边界。在它中调用setState()可以让你在下面的树中捕获未处理的JavaScript错误,并显示一个后备UI。只能使用错误边界从意外异常中恢复;不要试图将它们用于控制流程。详细
2018年12月12日
1 阅读
0 评论
0 点赞
2018-07-13
redux-persist数据持久化
import { createStore } from 'redux'; import { persistReducer } from 'redux-persist'; import storage from 'redux-persist/lib/storage'; import reducer from './reducer'; let persistedReducer = persistReducer({ key: 'auto', storage }, reducer); let store = createStore(persistedReducer); export default store;persistReducer(config, reducer)argumentsconfig objectrequired config: key, storagenotable other config: whitelist, blacklist, version, stateReconciler, debugreducer functionany reducer will work, typically this would be the top level reducer returned by combineReducersreturns an enhanced reducerimport React from "react"; import { HashRouter as Router, Route } from "react-router-dom"; import { PersistGate } from 'redux-persist/integration/react'; import { persistStore } from 'redux-persist'; import store from '../redux/store'; import { Provider } from 'react-redux'; import Index from '../redux/index'; import Login from '../redux/login'; let persistor = persistStore(store); export default ( <PersistGate loading={null} persistor={persistor}> <Router> <Provider store={store}> <div> <Route exact path='/' component={Index}></Route> <Route exact path='/login' component={Login}></Route> </div> </Provider> </Router> </PersistGate> );persistStore(store, [config, callback])argumentsstore redux store The store to be persisted.config object (typically null)callback function will be called after rehydration is finished.returns persistor objectpersistor objectthe persistor object is returned by persistStore with the following methods:.purge()purges state from disk and returns a promise.flush()immediately writes all pending state to disk and returns a promise.pause()pauses persistence.persist()resumes persistence官方文档
2018年07月13日
2 阅读
0 评论
0 点赞
2018-05-06
React按需加载
1.Bundle.js Bundle组件会接受一个名为 load 的 props load值一是一个组件异步加载的方法 load -> function (cb) {...cb(/ 异步加载的组件 /)},由bundle-loader封装 这个方法需要传入一个回调函数作为参数 回调函数会在在方法内异步接收加载完的组件import React from 'react'; class Bundle extends React.Component { constructor(props){ super(props); this.state = { // 默认为空 mod: null } } componentWillMount() { // 加载初始状态 this.load(this.props); } componentWillReceiveProps(nextProps) { if (nextProps.load !== this.props.load) { this.load(nextProps); } } load(props) { // 重置状态 this.setState({ mod: null }); // 传入组件的组件 props.load((mod) => { this.setState({ mod: mod.default ? mod.default : mod }); }); } render() { // 不为空,则渲染传入的子组件函数 return this.state.mod ? this.props.children(this.state.mod) : null; } } export default Bundle; 2.lazyLoad.js 这个包装函数接受两个值,一个为经过bundle-loader封装的组件,另一个是组件的属性import React from 'react'; import Bundle from './Bundle'; // 默认加载组件,可以直接返回 null const Loading = () => <div>Loading...</div>; /* 包装方法,第一次调用后会返回一个组件(函数式组件) 由于要将其作为路由下的组件,所以需要将 props 传入 <Bundle load={loadComponent}> {Comp => (Comp ? <Comp {...props} /> : <Loading />)} </Bundle> */ const lazyLoad = (loadComponent,props) => {//Bundle 包含的是一个函数子组件 由Bundle.js里的this.props.children(this.state.mod)渲染 return( <Bundle load={loadComponent}> {Comp => (Comp ? <Comp {...props} /> : <Loading />)} </Bundle> );} export default lazyLoad; 3.路由使用有两种使用方式,一种是webpack配置(见下面第四点),另一种是在组件内直接引用bundle-loader(下面代码没注释的就是)//import Home from './page/Home.bundle';//这种方式需要配置webpack的loader //import Detail from './page/Detail.bundle';//这种方式需要配置webpack的loader //-------------------------------------------------- import Detail from 'bundle-loader?lazy&name=home!./page/Detail.bundle'; import Home from 'bundle-loader?lazy&name=home!./page/Home.bundle'; <BrowserRouter> <div> <Route exact path="/" render={()=> <Redirect to="/home"/> // <Home dispatch={dispatch} getState={getState} questionList={value.question}></Home> }/> <Route path="/home" render={()=>{ return lazyLoad(Home, { dispatch:dispatch, getState:getState, questionList:value.question } ); }}/> <Route path="/detail" render={(props)=>{ return lazyLoad(Detail, { pid:props.location.id, questionList:value.question, dispatch:dispatch, answer:value.answer } ); }}/> </div> </BrowserRouter> 4.如果使用webpack配置注意这段代码要放在js的loader之前,不然可能会报错,这段配合下面这两句引用使用//import Home from './page/Home.bundle';//这种方式需要配置webpack的loader//import Detail from './page/Detail.bundle';//这种方式需要配置webpack的loader[html] view plain copy{ test: /\.bundle\.js$/, loader: 'bundle-loader', options: { lazy: true, name: '[name]' }
2018年05月06日
0 阅读
0 评论
0 点赞
2018-05-06
react-redux
Redux 是「React 全家桶」中极为重要的一员,它试图为 React 应用提供「可预测化的状态管理」机制。Redux 本身足够简单,除了 React,它还能够支持其他界面框架。所以如果要将 Redux 和 React 结合起来使用,就还需要一些额外的工具,其中最重要的莫过于 react-redux 了。react-redux 提供了两个重要的对象,Provider 和 connect,前者使 React 组件可被连接(connectable),后者把 React 组件和 Redux 的 store 真正连接起来。react-redux 的文档中,对 connect 的描述是一段晦涩难懂的英文,在初学 redux 的时候,我对着这段文档阅读了很久,都没有全部弄明白其中的意思(大概就是,单词我都认识,连起来啥意思就不明白了的感觉吧)。在使用了一段时间 redux 后,本文尝试再次回到这里,给这段文档(同时摘抄在附录中)一个靠谱的解读。预备知识首先回顾一下 redux 的基本用法。如果你还没有阅读过 redux 的文档,你一定要先去阅读一下。const reducer = (state = {count: 0}, action) => { switch (action.type){ case 'INCREASE': return {count: state.count + 1}; case 'DECREASE': return {count: state.count - 1}; default: return state; } } const actions = { increase: () => ({type: 'INCREASE'}), decrease: () => ({type: 'DECREASE'}) } const store = createStore(reducer); store.subscribe(() => console.log(store.getState()) ); store.dispatch(actions.increase()) // {count: 1} store.dispatch(actions.increase()) // {count: 2} store.dispatch(actions.increase()) // {count: 3}通过 reducer 创建一个 store,每当我们在 store 上 dispatch 一个 action,store 内的数据就会相应地发生变化。我们当然可以直接在 React 中使用 Redux:在最外层容器组件中初始化 store,然后将state 上的属性作为 props 层层传递下去。class App extends Component{ componentWillMount(){ store.subscribe((state)=>this.setState(state)) } render(){ return <Comp state={this.state} onIncrease={()=>store.dispatch(actions.increase())} onDecrease={()=>store.dispatch(actions.decrease())} /> } }但这并不是最佳的方式。最佳的方式是使用 react-redux 提供的 Provider 和 connect 方法。使用 react-redux首先在最外层容器中,把所有内容包裹在 Provider 组件中,将之前创建的 store 作为prop 传给 Provider。const App = () => { return ( <Provider store={store}> <Comp/> </Provider> ) };Provider 内的任何一个组件(比如这里的 Comp),如果需要使用 state 中的数据,就必须是「被 connect 过的」组件——使用 connect 方法对「你编写的组件(MyComp)」进行包装后的产物。class MyComp extends Component { // content... } const Comp = connect(...args)(MyComp);可见,connect 方法是重中之重。connect 详解究竟 connect 方法到底做了什么,我们来一探究竟。首先看下函数的签名:connect([mapStateToProps], [mapDispatchToProps], [mergeProps], [options])connect() 接收四个参数,它们分别是 mapStateToProps,mapDispatchToProps,mergeProps和options。mapStateToProps(state, ownProps) : stateProps这个函数允许我们将 store 中的数据作为 props 绑定到组件上。const mapStateToProps = (state) => { return { count: state.count } }这个函数的第一个参数就是 Redux 的 store,我们从中摘取了 count 属性。因为返回了具有 count 属性的对象,所以 MyComp 会有名为 count 的 props 字段。class MyComp extends Component { render(){ return <div>计数:{this.props.count}次</div> } } const Comp = connect(...args)(MyComp);当然,你不必将 state 中的数据原封不动地传入组件,可以根据 state 中的数据,动态地输出组件需要的(最小)属性。const mapStateToProps = (state) => { return { greaterThanFive: state.count > 5 } }函数的第二个参数 ownProps,是 MyComp 自己的 props。有的时候,ownProps 也会对其产生影响。比如,当你在 store 中维护了一个用户列表,而你的组件 MyComp 只关心一个用户(通过 props 中的 userId 体现)。const mapStateToProps = (state, ownProps) => { // state 是 {userList: [{id: 0, name: '王二'}]} return { user: _.find(state.userList, {id: ownProps.userId}) } } class MyComp extends Component { static PropTypes = { userId: PropTypes.string.isRequired, user: PropTypes.object }; render(){ return <div>用户名:{this.props.user.name}</div> } } const Comp = connect(mapStateToProps)(MyComp);当 state 变化,或者 ownProps 变化的时候,mapStateToProps 都会被调用,计算出一个新的stateProps,(在与 ownProps merge 后)更新给 MyComp。这就是将 Redux store 中的数据连接到组件的基本方式。mapDispatchToProps(dispatch, ownProps): dispatchPropsconnect 的第二个参数是 mapDispatchToProps,它的功能是,将 action 作为 props 绑定到MyComp 上。const mapDispatchToProps = (dispatch, ownProps) => { return { increase: (...args) => dispatch(actions.increase(...args)), decrease: (...args) => dispatch(actions.decrease(...args)) } } class MyComp extends Component { render(){ const {count, increase, decrease} = this.props; return (<div> <div>计数:{this.props.count}次</div> <button onClick={increase}>增加</button> <button onClick={decrease}>减少</button> </div>) } } const Comp = connect(mapStateToProps, mapDispatchToProps)(MyComp);由于 mapDispatchToProps 方法返回了具有 increase 属性和 decrease 属性的对象,这两个属性也会成为 MyComp 的 props。如上所示,调用 actions.increase() 只能得到一个 action 对象 {type:'INCREASE'},要触发这个 action 必须在 store 上调用 dispatch 方法。diapatch 正是 mapDispatchToProps 的第一个参数。但是,为了不让 MyComp 组件感知到 dispatch 的存在,我们需要将 increase 和decrease 两个函数包装一下,使之成为直接可被调用的函数(即,调用该方法就会触发dispatch)。Redux 本身提供了 bindActionCreators 函数,来将 action 包装成直接可被调用的函数。import {bindActionCreators} from 'redux'; const mapDispatchToProps = (dispatch, ownProps) => { return bindActionCreators({ increase: action.increase, decrease: action.decrease }); }同样,当 ownProps 变化的时候,该函数也会被调用,生成一个新的 dispatchProps,(在与statePrope 和 ownProps merge 后)更新给 MyComp。注意,action 的变化不会引起上述过程,默认 action 在组件的生命周期中是固定的。[mergeProps(stateProps, dispatchProps, ownProps): props]之前说过,不管是 stateProps 还是 dispatchProps,都需要和 ownProps merge 之后才会被赋给 MyComp。connect 的第三个参数就是用来做这件事。通常情况下,你可以不传这个参数,connect 就会使用 Object.assign 替代该方法。其他最后还有一个 options 选项,比较简单,基本上也不大会用到(尤其是你遵循了其他的一些 React 的「最佳实践」的时候),本文就略过了。希望了解的同学可以直接看文档。注意:和react-router混用时不能把route, connect(mapStateToProps, mapDispatchToProps)(route).必须分发最高层主件原文
2018年05月06日
2 阅读
0 评论
0 点赞
2018-05-06
React-Router V4
react-router-dom:使用 HTML5 提供的 history API 来保持 UI 和 URL 的同步;:使用 URL 的 hash (例如:window.location.hash) 来保持 UI 和 URL 的同步;:能在内存保存你 “URL” 的历史纪录(并没有对地址栏读写);:从不会改变地址;Route主件path(string): 路由匹配路径。(没有path属性的Route 总是会 匹配);exact(bool):为true时,则要求路径与location.pathname必须完全匹配;strict(bool):true的时候,有结尾斜线的路径只能匹配有斜线的location.pathname;Route渲染方式:在地址匹配的时候React的组件才会被渲染,route props也会随着一起被渲染;:这种方式对于内联渲染和包装组件却不引起意料之外的重新挂载特别方便;:与render属性的工作方式基本一样,除了它是不管地址匹配与否都会被调用;Switch主件的独特之处是独它仅仅渲染一个路由。相反地,每一个包含匹配地址(location)的都会被渲染。思考下面的代码路由嵌套V4的嵌套,和V2V3相当不同V4必须在主件的内部嵌套route例子如下:import React, {Component} from 'react'; import {Route} from 'react-router-dom'; import lazyLoad from '../lazyLoad'; import Index from 'bundle-loader?lazy&name=home!./index'; export default class Login extends Component { render() { return ( <div>测试 <Route path={this.props.match.path + '/index'} render={() => { return lazyLoad(Index, { ...this.props }); }}></Route> </div> ); } }
2018年05月06日
5 阅读
0 评论
0 点赞
2018-05-04
React-eslint
eslint配置npm install --save-dev eslint eslint-plugin-html eslint-plugin-import eslint-plugin-node babel-eslint eslint-plugin-reactmodule.exports = { "parser": "babel-eslint", "plugins": [ "react" ], "parserOptions": { "ecmaVersion": 6, "sourceType": "module", "ecmaFeatures": { "jsx": true } }, "env": { "browser": true, "amd": true, "es6": true, "node": true, "mocha": true }, "rules": { "comma-dangle": 1, "quotes": [0, "single"], "no-undef": 1, "global-strict": 0, "no-extra-semi": 1, "no-underscore-dangle": 0, "no-console": 1, "no-unused-vars": 1, "no-trailing-spaces": [1, { "skipBlankLines": true }], "no-unreachable": 1, "no-alert": 0, "react/jsx-uses-react": 1, "react/jsx-uses-vars": 1, "no-extra-semi": 1, //禁止多余的冒号 "no-implicit-coercion": 1, //禁止隐式转换 "no-multi-spaces": 1, //不能用多余的空格 "no-trailing-spaces": 1, //一行结束后面不要有空格 "no-undef": 1, //不能有未定义的变量 "no-unused-vars": [2, { "vars": "all", "args": "after-used" }], //不能有声明后未被使用的变量或参数 "brace-style": [1, "1tbs"], //大括号风格 "callback-return": 1, //避免多次调用回调什么的 "comma-dangle": [2, "never"], //对象字面量项尾不能有逗号 "indent": [1, 2], //缩进风格 "new-parens": 2, //new时必须加小括号 "max-params": [1, 3], //函数最多只能有3个参数 "new-cap": 2, //函数名首行大写必须使用new方式调用,首行小写必须用不带new方式调用 "quote-props": [0, "always"], //对象字面量中的属性名是否强制双引号 "vars-on-top": 2, //var必须放在作用域顶部 //空行最多不能超过100行 "no-multiple-empty-lines": [2, { "max": 1 }], "semi": [1, "always"] //语句强制分号结尾 } }
2018年05月04日
2 阅读
0 评论
0 点赞
2017-12-16
React无限极菜单生成
//创建无菜单 generateMenu(menuObj) { let vdom = []; if (menuObj instanceof Array) { //判断是否为数组 let list = []; for (var item of menuObj) { //把数组内的数据再次传入(递归点) list.push(this.generateMenu(item)); } //为数组就添加ULul vdom.push( <ul key='single'> {list} </ul> ); } else { if(!menuObj){ return } //为对象时添加li vdom.push( <li key={menuObj.ModuleId} data-id={menuObj.ModuleId}> {menuObj.ModuleName} {this.generateMenu(menuObj.child)} </li> ); } return vdom; }
2017年12月16日
1 阅读
0 评论
0 点赞
2017-12-04
react的事件合成
在开发react项目中遇见在调用事件函数里的异步方法访问事件属性时报错.官方给出了解决方法.如果要以异步方式访问事件属性,应该对事件调用 event.persist() ,这将从池中删除合成事件,并允许用户代码保留对事件的引用。
2017年12月04日
0 阅读
0 评论
0 点赞
2017-11-28
Redux
§ Store首先要区分 store 和 statestate 是应用的状态,一般本质上是一个普通对象例如,我们有一个 Web APP,包含 计数器 和 待办事项 两大功能那么我们可以为该应用设计出对应的存储数据结构(应用初始状态): /** 应用初始 state,本代码块记为 code-1 **/ { counter: 0, todos: [] }store 是应用状态 state 的管理者,包含下列四个函数:getState() # 获取整个 state dispatch(action) # ※ 触发 state 改变的【唯一途径】※ subscribe(listener) # 您可以理解成是 DOM 中的 addEventListener replaceReducer(nextReducer) # 一般在 Webpack Code-Splitting 按需加载的时候用 二者的关系是:state = store.getState()Redux 规定,一个应用只应有一个单一的 store,其管理着唯一的应用状态 stateRedux 还规定,不能直接修改应用的状态 state,也就是说,下面的行为是不允许的:var state = store.getState()state.counter = state.counter + 1 // 禁止在业务逻辑中直接修改 state若要改变 state,必须 dispatch 一个 action,这是修改应用状态的不二法门现在您只需要记住 action 只是一个包含 type 属性的普通对象即可例如 { type: 'INCREMENT' }上面提到,state 是通过 store.getState() 获取,那么 store 又是怎么来的呢?想生成一个 store,我们需要调用 Redux 的 createStore:import { createStore } from 'redux' ... const store = createStore(reducer, initialState) // store 是靠传入 reducer 生成的哦!现在您只需要记住 reducer 是一个 函数,负责更新并返回一个新的 state而 initialState 主要用于前后端同构的数据同步(详情请关注 React 服务端渲染)§ Action上面提到,action(动作)实质上是包含 type 属性的普通对象,这个 type 是我们实现用户行为追踪的关键例如,增加一个待办事项 的 action 可能是像下面一样:/** 本代码块记为 code-2 **/ { type: 'ADD_TODO', payload: { id: 1, content: '待办事项1', completed: false } }当然,action 的形式是多种多样的,唯一的约束仅仅就是包含一个 type 属性罢了也就是说,下面这些 action 都是合法的:/** 如下都是合法的,但就是不够规范 **/ { type: 'ADD_TODO', id: 1, content: '待办事项1', completed: false } { type: 'ADD_TODO', abcdefg: { id: 1, content: '待办事项1', completed: false } }虽说没有约束,但最好还是遵循规范如果需要新增一个代办事项,实际上就是将 code-2 中的 payload “写入” 到 state.todos 数组中(如何“写入”?在此留个悬念):/** 本代码块记为 code-3 **/ { counter: 0, todos: [{ id: 1, content: '待办事项1', completed: false }] }刨根问底,action 是谁生成的呢?⊙ Action CreatorAction Creator 可以是同步的,也可以是异步的顾名思义,Action Creator 是 action 的创造者,本质上就是一个函数,返回值是一个 action(对象)例如下面就是一个 “新增一个待办事项” 的 Action Creator:/** 本代码块记为 code-4 **/ var id = 1 function addTodo(content) { return { type: 'ADD_TODO', payload: { id: id++, content: content, // 待办事项内容 completed: false // 是否完成的标识 } } }将该函数应用到一个表单(假设 store 为全局变量,并引入了 jQuery ):<--! 本代码块记为 code-5 --> <input type="text" id="todoInput" /> <button id="btn">提交</button> <script> $('#btn').on('click', function() { var content = $('#todoInput').val() // 获取输入框的值 var action = addTodo(content) // 执行 Action Creator 获得 action store.dispatch(action) // 改变 state 的不二法门:dispatch 一个 action!!! }) </script> 在输入框中输入 “待办事项2” 后,点击一下提交按钮,我们的 state 就变成了: /** 本代码块记为 code-6 **/ { counter: 0, todos: [{ id: 1, content: '待办事项1', completed: false }, { id: 2, content: '待办事项2', completed: false }] }通俗点讲,Action Creator 用于绑定到用户的操作(点击按钮等),其返回值 action 用于之后的 dispatch(action)刚刚提到过,action 明明就没有强制的规范,为什么 store.dispatch(action) 之后,Redux 会明确知道是提取 action.payload,并且是对应写入到 state.todos 数组中?又是谁负责“写入”的呢?悬念即将揭晓...§ ReducerReducer 必须是同步的纯函数用户每次 dispatch(action) 后,都会触发 reducer 的执行reducer 的实质是一个函数,根据 action.type 来更新 state 并返回 nextState最后会用 reducer 的返回值 nextState 完全替换掉原来的 state注意:上面的这个 “更新” 并不是指 reducer 可以直接对 state 进行修改Redux 规定,须先复制一份 state,在副本 nextState 上进行修改操作例如,可以使用 lodash 的 cloneDeep,也可以使用 Object.assign / map / filter/ ... 等返回副本的函数在上面 Action Creator 中提到的 待办事项的 reducer 大概是长这个样子 (为了容易理解,在此不使用 ES6 / Immutable.js):/** 本代码块记为 code-7 **/ var initState = { counter: 0, todos: [] } function reducer(state, action) { // ※ 应用的初始状态是在第一次执行 reducer 时设置的 ※ if (!state) state = initState switch (action.type) { case 'ADD_TODO': var nextState = _.cloneDeep(state) // 用到了 lodash 的深克隆 nextState.todos.push(action.payload) return nextState default: // 由于 nextState 会把原 state 整个替换掉 // 若无修改,必须返回原 state(否则就是 undefined) return state } }通俗点讲,就是 reducer 返回啥,state 就被替换成啥§ 总结store 由 Redux 的 createStore(reducer) 生成state 通过 store.getState() 获取,本质上一般是一个存储着整个应用状态的对象action 本质上是一个包含 type 属性的普通对象,由 Action Creator (函数) 产生改变 state 必须 dispatch 一个 actionreducer 本质上是根据 action.type 来更新 state 并返回 nextState 的函数reducer 必须返回值,否则 nextState 即为 undefined实际上,state 就是所有 reducer 返回值的汇总(本教程只有一个 reducer,主要是应用场景比较简单)Action Creator => action => store.dispatch(action) => reducer(state, action) => 原 state state = nextState⊙ Redux 与传统后端 MVC 的对照Redux 传统后端 MVC store 数据库实例 state 数据库中存储的数据 dispatch(action) 用户发起请求 action: { type, payload } type 表示请求的 URL,payload 表示请求的数据 reducer 路由 + 控制器(handler) reducer 中的 switch-case 分支 路由,根据 action.type 路由到对应的控制器 reducer 内部对 state 的处理 控制器对数据库进行增删改操作 reducer 返回 nextState 将修改后的记录写回数据库
2017年11月28日
0 阅读
0 评论
0 点赞
2017-11-25
React-Router
一、基本用法React Router 安装命令如下。$ npm install -S react-router使用时,路由器Router就是React的一个组件。import { Router } from 'react-router'; render(<Router/>, document.getElementById('app'));Router组件本身只是一个容器,真正的路由要通过Route组件定义。import { Router, Route, hashHistory } from 'react-router'; render(( <Router history={hashHistory}> <Route path="/" component={App}/> </Router> ), document.getElementById('app')); 上面代码中,用户访问根路由/(比如http://www.example.com/),组件APP就会加载到document.getElementById('app')。 你可能还注意到,Router组件有一个参数history,它的值hashHistory表示,路由的切换由URL的hash变化决定,即URL的#部分发生变化。举例来说,用户访问http://www.example.com/,实际会看到的是http://www.example.com/#/。 Route组件定义了URL路径与组件的对应关系。你可以同时使用多个Route组件。 <Router history={hashHistory}> <Route path="/" component={App}/> <Route path="/repos" component={Repos}/> <Route path="/about" component={About}/> </Router>上面代码中,用户访问/repos(比如http://localhost:8080/#/repos)时,加载Repos组件;访问/about(http://localhost:8080/#/about)时,加载About组件。二、嵌套路由Route组件还可以嵌套。<Router history={hashHistory}> <Route path="/" component={App}> <Route path="/repos" component={Repos}/> <Route path="/about" component={About}/> </Route> </Router>上面代码中,用户访问/repos时,会先加载App组件,然后在它的内部再加载Repos组件。<App> <Repos/> </App>App组件要写成下面的样子。export default React.createClass({ render() { return <div> {this.props.children} </div> } })上面代码中,App组件的this.props.children属性就是子组件。子路由也可以不写在Router组件里面,单独传入Router组件的routes属性。let routes = <Route path="/" component={App}> <Route path="/repos" component={Repos}/> <Route path="/about" component={About}/> </Route>; <Router routes={routes} history={browserHistory}/>三、 path 属性Route组件的path属性指定路由的匹配规则。这个属性是可以省略的,这样的话,不管路径是否匹配,总是会加载指定组件。请看下面的例子。<Route path="inbox" component={Inbox}> <Route path="messages/:id" component={Message} /> </Route>上面代码中,当用户访问/inbox/messages/:id时,会加载下面的组件。<Inbox> <Message/> </Inbox>如果省略外层Route的path参数,写成下面的样子。<Route component={Inbox}> <Route path="inbox/messages/:id" component={Message} /> </Route>现在用户访问/inbox/messages/:id时,组件加载还是原来的样子。<Inbox> <Message/> </Inbox>四、通配符path属性可以使用通配符。<Route path="/hello/:name"> // 匹配 /hello/michael // 匹配 /hello/ryan <Route path="/hello(/:name)"> // 匹配 /hello // 匹配 /hello/michael // 匹配 /hello/ryan <Route path="/files/*.*"> // 匹配 /files/hello.jpg // 匹配 /files/hello.html <Route path="/files/*"> // 匹配 /files/ // 匹配 /files/a // 匹配 /files/a/b <Route path="/**/*.jpg"> // 匹配 /files/hello.jpg // 匹配 /files/path/to/file.jpg通配符的规则如下。(1):paramName:paramName匹配URL的一个部分,直到遇到下一个/、?、#为止。这个路径参数可以通过this.props.params.paramName取出。(2)()()表示URL的这个部分是可选的。(3)**匹配任意字符,直到模式里面的下一个字符为止。匹配方式是非贪婪模式。(4) **** 匹配任意字符,直到下一个/、?、#为止。匹配方式是贪婪模式。path属性也可以使用相对路径(不以/开头),匹配时就会相对于父组件的路径,可以参考上一节的例子。嵌套路由如果想摆脱这个规则,可以使用绝对路由。路由匹配规则是从上到下执行,一旦发现匹配,就不再其余的规则了。上面代码中,路径/comments同时匹配两个规则,第二个规则不会生效。设置路径参数时,需要特别小心这一点。<Router> <Route path="/:userName/:id" component={UserPage}/> <Route path="/about/me" component={About}/> </Router>上面代码中,用户访问/about/me时,不会触发第二个路由规则,因为它会匹配/:userName/:id这个规则。因此,带参数的路径一般要写在路由规则的底部。此外,URL的查询字符串/foo?bar=baz,可以用this.props.location.query.bar获取。五、IndexRoute 组件下面的例子,你会不会觉得有一点问题?<Router> <Route path="/" component={App}> <Route path="accounts" component={Accounts}/> <Route path="statements" component={Statements}/> </Route> </Router>上面代码中,访问根路径/,不会加载任何子组件。也就是说,App组件的this.props.children,这时是undefined。因此,通常会采用{this.props.children || <Home/>}这样的写法。这时,Home明明是Accounts和Statements的同级组件,却没有写在Route中。IndexRoute就是解决这个问题,显式指定Home是根路由的子组件,即指定默认情况下加载的子组件。你可以把IndexRoute想象成某个路径的index.html。<Router> <Route path="/" component={App}> <IndexRoute component={Home}/> <Route path="accounts" component={Accounts}/> <Route path="statements" component={Statements}/> </Route> </Router>现在,用户访问/的时候,加载的组件结构如下。<App> <Home/> </App>这种组件结构就很清晰了:App只包含下级组件的共有元素,本身的展示内容则由Home组件定义。这样有利于代码分离,也有利于使用React Router提供的各种API。注意,IndexRoute组件没有路径参数path。六、Redirect 组件组件用于路由的跳转,即用户访问一个路由,会自动跳转到另一个路由。<Route path="inbox" component={Inbox}> {/* 从 /inbox/messages/:id 跳转到 /messages/:id */} <Redirect from="messages/:id" to="/messages/:id" /> </Route>现在访问/inbox/messages/5,会自动跳转到/messages/5。七、IndexRedirect 组件IndexRedirect组件用于访问根路由的时候,将用户重定向到某个子组件。<Route path="/" component={App}> <IndexRedirect to="/welcome" /> <Route path="welcome" component={Welcome} /> <Route path="about" component={About} /> </Route>上面代码中,用户访问根路径时,将自动重定向到子组件welcome。八、LinkLink组件用于取代元素,生成一个链接,允许用户点击后跳转到另一个路由。它基本上就是元素的React 版本,可以接收Router的状态。render() { return <div> <ul role="nav"> <li><Link to="/about">About</Link></li> <li><Link to="/repos">Repos</Link></li> </ul> </div> }如果希望当前的路由与其他路由有不同样式,这时可以使用Link组件的activeStyle属性。<Link to="/about" activeStyle={{color: 'red'}}>About</Link> <Link to="/repos" activeStyle={{color: 'red'}}>Repos</Link>上面代码中,当前页面的链接会红色显示。另一种做法是,使用activeClassName指定当前路由的Class。<Link to="/about" activeClassName="active">About</Link> <Link to="/repos" activeClassName="active">Repos</Link>上面代码中,当前页面的链接的class会包含active。在Router组件之外,导航到路由页面,可以使用浏览器的History API,像下面这样写。import { browserHistory } from 'react-router'; browserHistory.push('/some/path');九、IndexLink如果链接到根路由/,不要使用Link组件,而要使用IndexLink组件。这是因为对于根路由来说,activeStyle和activeClassName会失效,或者说总是生效,因为/会匹配任何子路由。而IndexLink组件会使用路径的精确匹配。<IndexLink to="/" activeClassName="active"> Home </IndexLink>上面代码中,根路由只会在精确匹配时,才具有activeClassName。另一种方法是使用Link组件的onlyActiveOnIndex属性,也能达到同样效果。<Link to="/" activeClassName="active" onlyActiveOnIndex={true}> Home </Link>实际上,IndexLink就是对Link组件的onlyActiveOnIndex属性的包装。十、histroy 属性Router组件的history属性,用来监听浏览器地址栏的变化,并将URL解析成一个地址对象,供 React Router 匹配。history属性,一共可以设置三种值。browserHistory hashHistory createMemoryHistory如果设为hashHistory,路由将通过URL的hash部分(#)切换,URL的形式类似example.com/#/some/path。import { hashHistory } from 'react-router' render( <Router history={hashHistory} routes={routes} />, document.getElementById('app') )如果设为browserHistory,浏览器的路由就不再通过Hash完成了,而显示正常的路径example.com/some/path,背后调用的是浏览器的History API。import { browserHistory } from 'react-router' render( <Router history={browserHistory} routes={routes} />, document.getElementById('app') )但是,这种情况需要对服务器改造。否则用户直接向服务器请求某个子路由,会显示网页找不到的404错误。如果开发服务器使用的是webpack-dev-server,加上--history-api-fallback参数就可以了。$ webpack-dev-server --inline --content-base . --history-api-fallbackcreateMemoryHistory主要用于服务器渲染。它创建一个内存中的history对象,不与浏览器URL互动。const history = createMemoryHistory(location)十一、表单处理Link组件用于正常的用户点击跳转,但是有时还需要表单跳转、点击按钮跳转等操作。这些情况怎么跟React Router对接呢?下面是一个表单。<form onSubmit={this.handleSubmit}> <input type="text" placeholder="userName"/> <input type="text" placeholder="repo"/> <button type="submit">Go</button> </form>第一种方法是使用browserHistory.pushimport { browserHistory } from 'react-router' // ... handleSubmit(event) { event.preventDefault() const userName = event.target.elements[0].value const repo = event.target.elements[1].value const path = `/repos/${userName}/${repo}` browserHistory.push(path) },第二种方法是使用context对象。export default React.createClass({ // ask for `router` from context contextTypes: { router: React.PropTypes.object }, handleSubmit(event) { // ... this.context.router.push(path) }, })十二、路由的钩子每个路由都有Enter和Leave钩子,用户进入或离开该路由时触发。<Route path="about" component={About} /> <Route path="inbox" component={Inbox}> <Redirect from="messages/:id" to="/messages/:id" /> </Route>上面的代码中,如果用户离开/messages/:id,进入/about时,会依次触发以下的钩子。/messages/:id的onLeave/inbox的onLeave/about的onEnter下面是一个例子,使用onEnter钩子替代组件。<Route path="inbox" component={Inbox}> <Route path="messages/:id" onEnter={ ({params}, replace) => replace(`/messages/${params.id}`) } /> </Route>onEnter钩子还可以用来做认证。const requireAuth = (nextState, replace) => { if (!auth.isAdmin()) { // Redirect to Home page if not an Admin replace({ pathname: '/' }) } } export const AdminRoutes = () => { return ( <Route path="/admin" component={Admin} onEnter={requireAuth} /> ) }下面是一个高级应用,当用户离开一个路径的时候,跳出一个提示框,要求用户确认是否离开。const Home = withRouter( React.createClass({ componentDidMount() { this.props.router.setRouteLeaveHook( this.props.route, this.routerWillLeave ) }, routerWillLeave(nextLocation) { // 返回 false 会继续停留当前页面, // 否则,返回一个字符串,会显示给用户,让其自己决定 if (!this.state.isSaved) return '确认要离开?'; }, }) )上面代码中,setRouteLeaveHook方法为Leave钩子指定routerWillLeave函数。该方法如果返回false,将阻止路由的切换,否则就返回一个字符串,提示用户决定是否要切换。文章来自阮一峰的网络日志!
2017年11月25日
4 阅读
0 评论
0 点赞
1
2