zoukankan      html  css  js  c++  java
  • 手把手教你全家桶之React(二)

    前言

    上一篇已经讲了一些react的基本配置,本遍接着讲热更新以及react+redux的配置与使用。

    热更新

    我们在实际开发时,都有用到热更新,在修改代码后,不用每次都重启服务,而是自动更新。并而不是让浏览器刷新,只是刷新了我们所改代码影响到的模块。
    关于热更新的配置,可看介绍戳这里

    因为我们用了webpack-dev-server,我们可以不需要向上图一样配置,只需要修改启动配置以修改默认值,--hot项。

        "start": "webpack-dev-server --config webpack.dev.config.js --color --progress --hot"
    

    然后要做的是当模块更新后,通知入口文件index.js。我们看官网的教程配置

    打开src/index.js,如上图配置

    import React from 'react';
    import ReactDom from 'react-dom';
    import getRouter from './router/router';
    
    if(module.hot){
        module.hot.accept();
    }
    ReactDom.render(
        getRouter(),
        document.getElementById?('app');
    )
    

    下面来试试重启后,修改Home或About组件,保存后是不是自动更新啦!

    到这里,你以为结束了吗,NO!NO!NO!在此我们成功为自己挖下了坑(说多了都是泪)。献上一段demo
    src/pages/Home/Home.js

    import React,{Component} from 'react';
    export default class Home extends Component{
        constructor(props){
            super(props);
            this.state={
                count:0
            }
            
        }
        _test(){
            this.setState({
                count:++this.state.count
            });
        }
        render(){
            return(
                <div>
                    <h1>当前共点击次数为:{this.state.count}</h1>
                    <button onClick={()=> this._test()}>点击我!</button>
                </div>
            )
        }
    }
    

    此时,按钮每点击一次,状态会自增,但是如果我们用热更新改一下文件,会发现,状态被清零了!!!显然这不是我们要的效果,那么我们平时在项目里为什么会用到react-hot-loader就明了了,因为可以保存状态。试试:
    安装依赖

    npm install react-hot-loader --save-dev
    

    官网介绍来配置

    • 首先是.babelrc文件
    {
        "plugins":["react-hot-loader/babel"]
    }
    
    • 修改 webpack.dev.config.js
        entry:[
            'react-hot-loader/patch',
            path.join(__dirname,'src/index.js')
        ]
    
    • 修改src/index.js
    import React from 'react';
    import ReactDom from 'react-dom';
    import getRouter from './router/router';
    import {AppContainer} from 'react-hot-loader';
    
    const hotLoader = RootElement => {
        ReactDom.render(
            <AppContainer>
                {RootElement}
            </AppContainer>,
            document.getElementById('app')
        );
    }
    /*初始化*/
    hotLoader(getRouter());
    
    if(module.hot){
        module.hot.accept('./router/router',()=>{
            const getRouter=require('./router/router').default;
            hotLoader(getRouter());
        }); 
    }
    

    哇哦哇哦,成功保存状态啦,666!

    路径的优化

    上面的demo我们已经写过好几个组件了,发现在引用的时候都要用上相对路径,这样非常不方便。我们可以优化一下。
    我们以前做数学题总会寻找一些共同点提出来,这里也一样。我们的公共组件都放在了src/components文件目录下,业务组件都放在src/pages目录下。在webpack中,提供一个别名配置,让我们无论在哪个位置下,都通过别名从对应位置去读取文件。
    修改webpack.dev.config.js

    resolve:{
        alias:{
            pages:path.join(__dirname,'src/pages'),
            components:path.join(__dirname,'src/components'),
            router:path.join(__dirname,'src/router')
        }
    }
    

    然后按下面的形式改掉之前的路径

    /*之前*/
    import Home from '../pages/Home/Home';
    /*之后*/
    import Home from 'pages/Home/Home';
    

    看下改了路径后,是不是依然可以正常运行呢!

    Redux

    如果用react做过项目的,基本对redux就不陌生了吧。此文主讲全家桶的搭建,在此我就不详细解说。简单说下引用,做个小型计数器。

    • 安装
    npm install --save redux
    
    • 相关目录搭建
    cd src
    mkdir redux && cd redux
    mkdir actions
    mkdir reducers
    touch reducer.js
    touch store.js
    touch actions/counter.js
    touch reducers/counter.js
    
    • 增加文件的别名
      打开webpack.dev.config.js
    alias:{
        ...
        actions:path.join(__dirname,'src/redux/actions'),
        reducers:path.join(__dirname,'src/redux/reducers'),
        //redux:path.join(__dirname,'src/redux') 与模块重名
    }
    
    • 创建action,action是来描述不同的场景,通过触发action进入对应reducer
      打开文件src/redux/actions/counter.js
    export const INCREMENT = "counter/INCREMENT";
    export const DECREMENT = "counter/DECREMENT";
    export const RESET = "counter/RESET";
    
    export function increment(){
        return {type:INCREMENT}
    }
    export function decrement(){
        return {type:DECREMENT}
    }
    export function reset(){
        return {type:RESET}
    }
    
    • 接下来写reducers,用来接收action和旧的state,生成新的state
      src/redux/reducers/counter.js
    import {INCREMENT,DECREMENT,RESET} from '../actions/counter';
    const initState = {
        count : 0
    };
    
    export default function reducer(state=initState,action){
        switch(action.type){
            case INCREMENT:
                return {
                    count:state.count+1
                };
            case DECREMENT:
                return {
                    count:state.count-1
                };
            case RESET:
                return {
                    count:0
                };
            default:
                return state
        }
    }
    
    • 将所有的reducers合并到一起
      src/redux/reducers.js
    import counter from './rdeducers/counter';
    export default function combineReducers(state={},action){
        return {
            counter:counter(state.counter,action)
        }
    }
    
    • 创建store仓库,进行存取与监听state的操作
    1. 应用中state的保持
    2. getState()获取state
    3. dispatch(action)触发reducers,改变state
    4. subscribe(listener)注册监听器
      打开src/redux/store.js
    import {createStore} from 'redux';
    import combineReducers from './reducers.js';
    let store = createStore(combineReducers);
    export default store;
    
    • 测试
    cd src 
    cd redux
    touch testRedux.js
    

    打开src/redux/testRedux.js

    import {increment,decrement,reset} from './actions/counter';
    import store from './store';
    //初始值
    console.log(store.getState());
    //监听每次更新值
    let unsubscribe = store.subscribe(() =>
        console.log(store.getState())
    );
    //发起action
    store.dispatch(increment());
    store.dispatch(decrement());
    store.dispatch(reset());
    //停止监听
    unsubscribe();
    

    在当前目录下运行

    webpack testRedux.js build.js
    node build.js
    

    我这里报如下错误了

    经排查,发现是node版本的问题,我用nvm来作node版本管理工具,从原本的4.7切换到9.0的版本,运行正确。

    我们试用了一下redux,对于在项目熟用的童鞋来说,简直是没难度吧。那么回归正题,我们用redux搭配着react一起用。将上述counter改成一个组件。

    • 文件初始化搭建
    cd src/pages
    mkdir Counter
    touch Counter/Counter.js
    

    打开文件

    import React,{Component} from 'react';
    export default class Counter extends Component{
        render(){
            return(
                <div>
                    <h2>当前计数为:</h2>
                    <button onClick={
                        ()=>{
                            console.log('自增');
                        }
                    }>自增
                    </button>
                    <button onClick={()=>{
                        console.log('自减');
                    }}>自减
                    </button>
                    <button onClick={()=>{
                        console.log('重置')
                    }}>重置
                    </button>
                </div>
            )
        }
    }
    
    • 路由增加
      router/router.js
    import Home from 'pages/Home/Home';
    import About from 'pages/About/About';
    import Counter from 'pages/Counter/Counter';
    const getRouter=()=>(
        <Router>
            <div>
                <ul>
                    <li><Link to="/">Home</Link></li>
                    <li><Link to="/about">About</Link></li>
                    <li><Link to="counter">Counter</Link></li>
                </ul>
                <Switch>
                    <Route exact path="/" component={Home}/>
                    <Route path="/about" component={About}/>
                    <Route path="/counter" component={Counter}/>
                </Switch>
            </div>
        </Router>
    
    );
    export default getRouter;
    

    我们可以先跑一下,检查路由跳转是否正常。下面将redux应用到Counter组件上。

    react-redux

    • 安装 react-redux
    npm install --save react-redux
    
    • 组件的state绑定

    因为react-redux提供了connect方法,接收两个参数。

    1. mapStateToProps:把redux的state,转为组件的Props;
    2. mapDispatchToprops:触发actions的方法转为Props属性函数。
      connect()的作用有两个:一是从Redux的state中读取部分的数据,并通过props把这些数据返回渲染到组件中;二是传递dispatch(action)到props。
      打开 src/pages/Counter/Counter.js
    import React,{Component} from 'react';
    import {increment,decrement,reset} from 'actions/counter';
    import {connect} from 'react-redux';
    class Counter extends Component{
        render(){
            return(
                <div>
                    <h2>当前计数为:{this.props.counter.count}</h2>
                    <button onClick={()=>{
                        this.props.increment()
                    }}>自增</button>
                    <button onClick={()=>{
                        this.props.decrement()
                    }}>自减</button>
                    <button onClick={()=>{
                        this.props.reset()
                    }}>重置</button>
                </div>
            )
        }
    }
    const mapStateToProps = (state) => {
        return {
            counter:state.counter
        }
    };
    const mapDispatchToProps = (dispatch) => {
        return {
            increment:()=>{
                dispatch(increment())
            },
            decrement:()=>{
                dispatch(decrement())
            },
            reset:()=>{
                dispatch(reset())
            }
        }
    };
    export default connect(mapStateToProps,mapDispatchToProps)(Counter);
    
    • 调用的用的时候到src/index.js中,我们传入store
      注:我们引用react-redux中的Provider模块,它可以让所有的组件能访问到store,不用手动去传,也不用手动去监听。
    ...
    import {Provider} from 'react-redux';
    import store from './redux/store';
     
    const hotLoader = RootElement => {
        ReactDom.render(
            <AppContainer>
                <Provider store={store}>
                    {RootElement}
                </Provider>
            </AppContainer>,
            document.getElementById('app')
        );
    }
    ...
    

    然后我们运行下,效果如图

    异步action

    在实际开发中,我们更多的是用异步action,因为要前后端联合起来处理数据。
    正常我们去发起一个请求时,给用户呈现的大概步骤如下:

    1. 页面加载,请求发起,出现loading效果
    2. 请求成功,停止loading效果,data渲染
    3. 请求失败,停止loading效果,返回错误提示。

    下面我们模拟一个用户信息的get请求接口:

    • 创建文件
    cd dist
    mkdir api && cd api
    touch userInfo.json
    
    • 打开文件模拟数据
    {
        "name":"circle",
        "age":24,
        "like":"piano",
        "female":"girl"
    }
    
    • 创建action
    cd src/redux/actions
    touch userInfo.js
    

    在action中,我要需要创建三种状态:请求中,请求成功,请求失败。打开redux/actions/userInfo.js

    export const GET_USERINFO_REQUEST="userInfo/GET_USERINFO_REQUEST";
    export const GET_USERINFO_SUCCESS="userInfo/GET_USERINFO_SUCCESS";
    export const GET_USERINFO_FAIL="userInfo/GET_USERINFO_FAIL";
    
    export function getUserInfoRequest(){
        return {
            type:GET_USERINFO_REQUEST
        }
    }
    export function getUserInfoSuccess(userInfo){
        return{
            type:GET_USERINFO_SUCCESS,
            userInfo:userInfo
        }
    }
    export function getUserInfoFail(){
        return{
            type:GET_USERINFO_FAIL
        }
    }
    
    • 创建reducer
    cd src/redux/reducers
    touch userInfo.js
    

    打开文件

    import {GET_USERINFO_REQUEST,GET_USERINFO_SUCCESS,GET_USERINFO_FAIL} from 'actions/userInfo';
    
    const initState = {
        isLoading:false,
        userInfo:{},
        errMsg:''
    }
    
    export default function reducer(state=initState,action){
        switch(action.type){
            case GET_USERINFO_REQUEST:
                return{
                    ...state,
                    isLoading:true,
                    userInfo:{},
                    errMsg:''
                }
            case GET_USERINFO_SUCCESS:
                return{
                    ...state,
                    isLoading:false,
                    userInfo:action.userInfo,
                    errMsg:''
                }
            case GET_USERINFO_FAIL:
                return{
                    ...state,
                    isLoading:false,
                    userInfo:{},
                    errMsg:'请求出错'
                }
            default:
                return state;
        }
    }
    

    以上...state的意思是合并新旧的所有state可枚举项。

    • 与之前做计数器一样,接下来到src/redux/reducers.js中合并。
    import counter from 'reducers/counter';
    import userInfo from 'reducers/userInfo';
    
    export default function combineReducers(state = {}, action) {
        return {
            counter: counter(state.counter, action),
            userInfo:userInfo(state.userInfo,action)
        }
    }
    

    redux中提供了一个combineReducers函数来合并reducer,不需要我们自己写合并函数,在此我们对上面的reducers.js作下优化。

    import counter from 'reducers/counter';
    import userInfo from 'reducers/userInfo';
    import {combineReducers} from 'redux';
    
    export default combineReducers({
        counter,
        userInfo
    });
    
    • 接下来发起请求
      打开文件 src/redux/actions/userInfo.js,加入
    ...
    export function getUserInfo(){
        return function(dispatch){
            dispatch(getUserInfoRequest());
            return fetch('http://localhost:8000/api/userInfo.json')
                .then((response=>{
                    return response.json()
                }))
                .then((json)=>{
                    dispatch(getUserInfoSuccess(json))
                    }
                ).catch(()=>{
                    dispatch(getUserInfoFail());
                    }
                )
        }
    }
    

    之前我们做计数器时,与之对比现发action都是返回的对象,这里我们返回的是函数。
    为了让action可以返回函数,我们需要装新的依赖redux-tuhnk。它的作用是在action到reducer时作中间拦截,让action从函数的形式转为标准的对象形式,给reducer作正确处理。

    npm install --save redux-thunk
    
    • 引入redux-thunk,打开src/redux/store.js
      我们可以使用Redux提供的applyMiddleware方法来使用一个或者是多个中间件,将它作为createStore的第二个参数传入即可。
    import {createStore,applyMiddleware} from 'redux';
    import combineReducers from './reducers.js';
    import thunkMiddleware from 'redux-thunk';
    
    let store = createStore(combineReducers,applyMiddleware(thunkMiddleware));
    
    export default store;
    

    到这里我们基本的redux就搞定啦,下面写个组件来验证。

    cd src/pages
    mkdir UserInfo && cd UserInfo
    touch UserInfo.js
    

    打开文件

    import React,{Component} from 'react';
    import {connect} from 'react-redux';
    import {getUserInfo} from "actions/userInfo";
    
    class UserInfo extends Component{
        render(){
            const{userInfo,isLoading,errMsg} = this.props.userInfo;
            return(
                <div>
                    {
                        isLoading ? '请求中...' : 
                        (
                            errMsg ? errMsg :
                                <div>
                                    <h2>个人资料</h2>
                                    <ul>
                                        <li>姓名:{userInfo.name}</li>
                                        <li>年龄:{userInfo.age}</li>
                                        <li>爱好:{userInfo.like}</li>
                                        <li>性别:{userInfo.female}</li>
                                    </ul>
                                </div>
                        )
                    }
                    <button onClick={
                        ()=> this.props.getUserInfo()
                    }>查看个人资料</button>
                </div>
            )
        }
    }
    export default connect((state)=>({userInfo:state.userInfo}),{getUserInfo})(UserInfo);
    
    • 配置路由,src/router/router.js
    ...
    import React from 'react';
    import {BrowserRouter as Router,Route,Switch,Link} from 'react-router-dom';
    import Home from 'pages/Home/Home';
    import About from 'pages/About/About';
    import Counter from 'pages/Counter/Counter';
    import UserInfo from 'pages/UserInfo/UserInfo';
    
    const getRouter=()=>(
        <Router>
            <div>
                <ul>
                    <li><Link to="/">Home</Link></li>
                    <li><Link to="/about">About</Link></li>
                    <li><Link to="counter">Counter</Link></li>
                    <li><Link to="userinfo">UserInfo</Link></li>
                </ul>
            
                <Switch>
                    <Route exact path="/" component={Home}/>
                    <Route path="/about" component={About}/>
                    <Route path="/counter" component={Counter}/>
                    <Route path="/userinfo" component={UserInfo}/>
                </Switch>
            </div>
        </Router>
    
    );
    export default getRouter;
    
    
    • 运行效果如下

    未完待续 _ (偷偷告诉你,第三篇会讲一些优化哦!)

    我的博客即将搬运同步至腾讯云+社区,邀请大家一同入驻:https://cloud.tencent.com/developer/support-plan

  • 相关阅读:
    Saltstack module gem 详解
    Saltstack module freezer 详解
    Saltstack module firewalld 详解
    Saltstack module file 详解
    Saltstack module event 详解
    Saltstack module etcd 详解
    Saltstack module environ 详解
    Saltstack module drbd 详解
    Saltstack module dnsutil 详解
    获取主页_剥离百度
  • 原文地址:https://www.cnblogs.com/zhoulin-circle/p/9012729.html
Copyright © 2011-2022 走看看