How to convert React app to React-Redux app?

时间:2019-03-06 11:37:18

标签: javascript reactjs redux

I created a very simple app with only React and now I want to change it so, I can use Redux in it. (I know Redux is not needed here, but I'm just trying to learn Redux).

SearchBar.js - Only React

import React, { Component } from "react";

class SearchBar extends Component {
  state = {
    inputValue: ""
  };

  handleFormSubmit = e => {
    e.preventDefault();
    this.props.onSubmit(this.state.inputValue);
  };

  render() {
    return (
      <div className="header">
        <h1>Search for images on Unsplash</h1>
        <form onSubmit={this.handleFormSubmit} className="ui form">
          <input
            type="text"
            placeholder="Type here to search for images"
            value={this.state.inputValue}
            onChange={e => this.setState({ inputValue: e.target.value })}
          />
        </form>
      </div>
    );
  }
}

export default SearchBar;

App.js - Only React

import React, { Component } from "react";
import axios from "axios";
import SearchBar from "./components/SearchBar";
import ImageList from "./components/ImageList";

class App extends Component {
  state = {
    images: []
  };

  onSearchSubmit = async inputValue => {
    const API_KEY =
      "<MY API KEY FOR UNSPLASH>";

    const response = await axios.get(
      `https://api.unsplash.com/search/photos?page=1&query=${inputValue}&client_id=${API_KEY}`
    );

    this.setState({ images: response.data.results });
  };

  render() {
    return (
      <>
        <SearchBar onSubmit={this.onSearchSubmit} />
        <div>
          <ImageList images={this.state.images} />
        </div>
      </>
    );
  }
}

export default App;

Using Redux

I put the redux-version on codeSandBox. Of course it's not working yet.

Here are my changes so far:

App.js with redux

import React, { Component } from "react";
import { Provider } from "react-redux";
import store from "./store";

import SearchBar from "./components/SearchBar";
import ImageList from "./components/ImageList";
import "./app.scss";

class App extends Component {
  render() {
    return (
      <Provider store={store}>
        <SearchBar onSubmit={this.onSearchSubmit} />
        <div>
          <ImageList images={this.state.images} />
        </div>
      </Provider>
    );
  }
}

export default App;

store.js

import { createStore, applyMiddleware, compose } from "redux";
import thunk from "redux-thunk";
import rootReducer from "./reducers";

const initialState = {};

const middleware = [thunk];

const store = createStore(
  rootReducer,
  initialState,
  compose(
    applyMiddleware(...middleware),
    window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
  )
);

export default store;

fetchAction.js

import axios from "axios";

export const FETCH_DATA = "fetch_data";

// Getting all images
export const getImages = inputValue => async dispatch => {
  const API_KEY =
    "<MY API KEY FOR UNSPLASH>";
  const res = await axios.get(
    `https://api.unsplash.com/search/photos?page=1&query=${inputValue}&client_id=${API_KEY}`
  );
  console.log(res.data.results);

  dispatch({
    type: FETCH_DATA,
    payload: res.data.results
  });
};

index.js inside reducers folder

import { combineReducers } from "redux";
import fetchReducer from "./fetchReducer";

export default combineReducers({
  images: fetchReducer
});

fetchReducer.js

import { FETCH_DATA } from "../actions/fetchAction";

const initialState = {};

export default function(state = initialState, action) {
  switch (action.type) {
    case FETCH_DATA:
      return {
        ...state
      };

    default:
      return state;
  }
}

But, I have two questions:

  1. Where should I use connect? in App.js or in SearchBar.js?
  2. If I add the following to my component, where I use connect:

    const mapStateToProps = state => ({ images: });

    export default connect( mapStateToProps, { getImages } )(SearchBar);

What would be the value of images inside mapStateToProps?

I put the redux-version on codeSandBox. Of course it's not working yet.

3 个答案:

答案 0 :(得分:0)

建议的标准/常见过程是在单独的容器文件中设置连接的逻辑。这将包含您的连接函数以及所有mapStateToProps,mapDispatchToProps等

您的容器将如下所示:

import SearchBar from "./components/SearchBar"

const mapStateToProps = state => ({ 
  images: images(state)
});

export default connect(mapStateToProps)(SearchBar)

答案 1 :(得分:0)

1)您应该在 SearchBar 中使用connect 2)这取决于您调用API调用以获取图像的位置。 尝试这样的操作,您很有可能必须在Gallery组件内调用this.props.getImages(..),很可能是在您键入内容

import React, { Component } from "react";
import { connect } from "react-redux";
import { getImages } from "../actions/fetchAction";
import Masonry from "react-masonry-component";

const masonryOptions = {
  transitionDuration: 1
};

const imagesLoadedOptions = { background: ".my-bg-image-el" };

class Gallery extends Component {
  childElements = () => {
    if (this.props.images) {
      return this.props.images.map(item => {
        return (
          <li className="masonry_item" key={item.id}>
            <img
              src={item.urls.regular}
              alt={item.description}
              className="masonry_item_img"
            />
          </li>
        );
      });
    }
    return <div>no images</div>;
  };

  render() {
    console.log(this.props);
    // map method generates a new Array

    return (
      <Masonry
        className={"masonry"} // default ''
        elementType={"ul"} // default 'div'
        options={masonryOptions} // default {}
        disableImagesLoaded={false} // default false
        updateOnEachImageLoad={false} // default false and works only if disableImagesLoaded is false
        imagesLoadedOptions={imagesLoadedOptions} // default {}
      >
        {this.childElements()}
      </Masonry>
    );
  }
}

let mapStateToProps = (state, props) => {
  return {
    images: state.images.images
  };
};

let mapDispatchToProps = dispatch => {
  return {
    getImages: data => {
      dispatch(getImages(data));
    }
  };
};

export default connect(
  mapStateToProps,
  mapDispatchToProps
)(Gallery);

答案 2 :(得分:0)

对于您的store.js,而不是:

import { createStore, applyMiddleware, compose } from "redux";
import thunk from "redux-thunk";
import rootReducer from "./reducers";

const initialState = {};

const middleware = [thunk];

const store = createStore(
  rootReducer,
  initialState,
  compose(
    applyMiddleware(...middleware),
    window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
  )
);

export default store;

尝试:

import React from "react";
import ReactDOM from "react-dom";
import { Provider } from "react-redux";
import { createStore, applyMiddleware } from "redux";
import thunk from 'redux-thunk';

import App from "./components/App";
import reducers from "./reducers";

const store = createStore(reducers, applyMiddleware(thunk));

ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.querySelector("#root")
);

然后在您的App.js文件中,而不是:

import React, { Component } from "react";
import { Provider } from "react-redux";
import store from "./store";
import SearchBar from "./components/SearchBar";
import ImageList from "./components/ImageList";
import "./app.scss";

class App extends Component {
  render() {
    return (
      <Provider store={store}>
        <SearchBar onSubmit={this.onSearchSubmit} />
        <div>
          <ImageList images={this.state.images} />
        </div>
      </Provider>
    );
  }
}

export default App;

尝试:

import React, { Component } from "react";
import SearchBar from "./components/SearchBar";
import ImageList from "./components/ImageList";
import "./app.scss";

class App extends Component {
  render() {
    return (
      <div>
        <SearchBar onSubmit={this.onSearchSubmit} />
        <div>
          <ImageList images={this.state.images} />
        </div>
      </div>
    );
  }
}

export default App;

对于您的Axios请求,不要将所有代码都扔到fetchActions.js内,而要创建一个文件夹/文件结构apis/unsplash.js

import axios from 'axios';

export default axios.create({
    baseURL: 'https://api.unsplash.com'
});

然后在您的fetchActions.js中:

export const FETCH_DATA = "fetch_data";

// Getting all images
export const getImages = inputValue => async dispatch => {
  const API_KEY =
    "<MY API KEY FOR UNSPLASH>";
  const res = await unsplash.get(
    `/search/photos?page=1&query=${inputValue}&client_id=${API_KEY}`
  );
  console.log(res.data.results);

  dispatch({
    type: "FETCH_DATA",
    payload: res.data.results
  });
};

您的combineReducers看起来不错。 您的fetchReducer.js,我经常在商业应用中看到:const initialState = {};

这并不是真正必要,只需将initialState中间人删掉即可:

import { FETCH_DATA } from "../actions/fetchAction";

export default function(state = {}, action) {
  switch (action.type) {
    case "FETCH_DATA":
      return {
        ...state
      };

    default:
      return state;
  }
}

漂亮,干净,优雅。现在,在哪里使用连接?在AppSearchBar组件中?让我们问问自己,connect函数的作用是什么?

让我们看一下,我们创建了Redux存储,然后将其传递给Provider,为什么这样做呢?嗯,是的,这样我们应用程序内的任何组件都可以通过Provider标签访问Redux存储。

换句话说,访问一些数据。那么什么组件需要访问一些数据? App?不,不是,它是层次结构中的父组件,只是将所有其他组件保持良好的连线,对吧?

但是我们的SearchBar将要通过该搜索栏访问一些数据,对吧?

现在,SearchBar组件可能不是您可能需要connect函数的唯一组件,但是我将从导入开始:

import { connect } from 'react-redux';

SearchBar的顶部,然后在SearchBar的底部,我将实现:

export default connect()(SearchBar)

到底是什么?为什么我们在SearchBar周围加上第二组括号?!

正如我曾试图向一位以前的学生解释的那样,该学生向管理员抱怨说我没有能力提供上面看到的那行代码,所以这行代码与执行此操作没有什么不同:

function connect() {
  return function() {
    return 'howdy!';
  }
}
connect();

哦,我的一个函数,它返回一个函数,但是等等,它什么都不会打印出来!

但是如果我立即加上第二个括号:

function connect() {
      return function() {
        return 'howdy!';
      }
    }
    connect()();

我得到了howdy!的打印件

我们在这里所做的就是返回一个函数,当我们调用要返回的函数时,我们将第二组括号放在其后,因此第二组括号将调用所返回的函数。

关于您的mapStateToProps,这里有些遗漏,我认为应该像这样:

const mapStateToProps = state => {
  return { images: state.images };
};