I wrote an article before, sharing the personal center page of a certain Product of Baidu that I reconstructed in my spare time using React+Redux technology stack. You can refer here, or to the Github repository address. In this engineering example, I used FIS3, an engineering construction tool in the factory, and the basic idea of React + Redux was adopted.

Today’s post is about a more complex, but very interesting, project called The News Early single-page app.

Recently, I noticed that the React Redux ecosystem project was active. But the quality of the works varies, and many popular projects not only fail to preach, but also mislead readers to some extent. I will elaborate in this article. Of course, MY own qualifications are shallow, the level is limited. I hope the great god can make it right.

I’ve hosted all the code for this project on my personal Github, where interested readers can talk to me.

At the same time, through this project example and this article, the details of the project development are explained step by step, and the optimization methods are included. The React STACK includes the React UI framework, Redux data flow framework, React Router, and Webpack.

Project background

During my study and work abroad, I have unimpeded access to news media such as BBC, CNN, ESPN, Le Figaro and so on, which is a great convenience and also one of my hobbies in my spare time. Even when traveling, watching these media channels (especially CNN) in your hotel is a great way to relax and relax…

Of course, the domestic environment is not very friendly to foreign media. Based on this, I designed and developed News Early.

This project is an instant headline news aggregator APP for more than 70 international well-known media including BBC, CNN, The NewYork Times and so on.

News Early is a simple and easy-to-use Web APP that gathers the headlines currently published on a range of news sources and blogs (70 and counting so far).

I used the following technology stacks and building tools for the whole project, including but not limited to: 1) React UI framework from Facebook; 2) JSX template; 3) Redux data stream design; 4) Webpack building tools; 5) Less preprocessor; .

The project design

Part of the experience of using the whole Web APP is presented in the following GIF diagram: (Please wait patiently for the GIF to load)

APP Use screenshot

  • The navigation bar at the top of the page includes a menu on the side bar and a page refresh button on the right.
  • The page content header rotation diagram supports automatic playback and gesture sliding control.
  • The main section of the page is the headlines of the corresponding news channel, usually about 10-20 items. Each item contains a news picture, a news introduction and a publish time.
  • Left folding menu bar function for news channel screening. 7 international media including BBC News, BBC Sport, CNN, ESPN, Financial Times, USA Today and MTV News.

Because I’m not in visual design, and I’m not in page interaction design (well, I’m just a coder). Therefore, in order to save time, I referred to the implementation of the seller network for the overall style of the APP, including the interface color.

Project structure and landing

Below, I would like to introduce the design composition and development details of the whole project.

Data flow state demonstration

Those familiar with Redux data flow framework should be familiar with store, Dispatch, Action, Reducer, middleware and other concepts. I’m not going to do that anymore. The most important part of this architecture is the design of data flow.

First, let’s take a look at the data flow and data structure of the project as a whole when the “Channel switch” interaction occurs:

Data Flow diagram

The directory structure

As shown in the figure:

The directory structure

The whole business code part of the project is divided into 9 UI components, 1 global Store and 1 Actions definition file.

  • App is the development directory
    • The Actions directory brings together all actions globally
    • The Components directory brings together all the UI components used globally
    • The Reducers directory brings together all the Reducers in the Redux architecture
    • The Store directory defines globally unique stores
    • The style directory centralizes the style files for all components globally
    • Main.js is a global entry function
  • Build is the packaged result directory
    • Index.html is the output page file
    • Bundle. js is the output of script files packaged in the bundle.js development directory
    • The img file defines the loading image when the APP is started
  • Node_modules depends on files… Other configuration files are not described in detail.

The 10 components include:

  • AppIndex: component container
  • BillboardCarousel: Page rotation diagram component
  • CurrentChanel: Page Headlines headlines component
  • HomeView: main page
  • ImagePlaceholder: placeholder component
  • Loading: Loading prompt components
  • NavBar: Top navigation component
  • SideBar: sideBar component
  • RouterWrap: a route-related component

Skeletons build

I think a big part of redux’s steep learning curve is the flow of data through it.

“Components dispatch various actions, and one-way data flow flows to the reducer. Reducer is a pure function (functional programming idea), receives and processes actions, returns new data, and then updates the component.”

This set of theories is not hard to understand.

However, the implementation of the project, especially with react, is not easy to do. Even if someone did it, the business would work, but it was the opposite of the core idea. I’ve seen a lot of projects in the community that are written indiscriminately, as long as they work, and are designed haphazardly to mislead beginners.

For example, there is a common problem of multiple stores throughout the project.

So why not recommend multiple stores? The answer can be found in the official FAQ. Readers familiar with the original model of Flux may know that there are multiple stores in Flux, and each store maintains different levels of data. The problem with this design is that one store is waiting for another store to do something about it. In Redux, we implement shard data hierarchies to avoid this situation. Maintaining a single store not only allows you to use Redux DevTools, but also simplifies persistence and deep processing of data and logical processing of subscriptions. With a single store approach, we don’t have to worry about store module import, Redux application packaging, and it will be easier to support server rendering in the future.

If the above paragraph is too abstract to understand, just look at my code implementation.

Define a globally unique store:

const store = createStore(
    combineReducers({
        sideBarChange,
        contents,
        routing: routerReducer
    }),
    composeEnhancers(applyMiddleware(thunkMiddleware)),
);Copy the code

I used Redux-Thunk as middleware to handle asynchronous actions. Thus, asynchronous processes are handled at the action level with no impact on the Component. ComposeEnhancers are set up to use redux devtool.

Container component construction:

const mapStateToProps = (state) = > {
    return {
        showLeftNav: state.sideBarChange.showLeftNav,
        loading: state.contents.loading,
        contents: state.contents.contents,
        currentChanel: state.contents.currentChanel
    }
}

var App = connect(mapStateToProps)(AppIndex);
render(
    <Provider store={store}>
        <Router history={history}>
            <Route path="/" component={App}>
                <Route path="home" component={HomeView}/>
            </Route>
        </Router>
    </Provider>,
    document.getElementById('app')
);Copy the code

I used react-Redux to connect. AppIndex is the only container component for the entire project. Dispatch the action and pass props down to the UI component (the puppet component).

If you don’t understand the difference between a container component and a UI component, check out the official documentation. These two concepts are extremely important in determining whether you can design an effective and sound component architecture.

Also, you’ll notice that I use the React-Router for route management. There is no need to use single-page routing for the entire project. This route management introduction, to be honest, is a bit of a chicken. But it doesn’t have any impact on the project. I brought him in for two main reasons.

  • The first is to follow up with secondary development, in order to allow for more product iterations, the use of route management is a must, we need to prepare for the long term.
  • And the other thing is, I’ve never used it before, and I want to try it.

Design of the actions

Actions, of course, are essential, so I’m going to take the most important “fetchContents” action creator and talk about it.

Pull data when you first enter the page and when you click on the left sidebar to select a news channel. For example, for the first rendering of the APP, “BBC News” News channel is loaded by default. After the page body component is mounted:

componentDidMount() {
    // Get the content
    this.props.fetchContents('bbc-news');
}Copy the code

The fetchContents method is called up and uploaded step by step to the container component. Dispatch by container component:

fetchContents={(source)=>{this.props.dispatch(action.fetchContents(source))}}Copy the code

Source indicates the news channel of Latori. ‘BBC-news’, of course.

In the actions.js file, the asynchronous action is processed and the data is pulled. Here, I use the latest FETCH API to replace the old XHR, and use fetch’s promise concept to encapsulate a layer of _GET methods for AJAX asynchronous requests:

const sendByGet = ({url}, dispatch) = > {
let finalUrl = url + '&apiKey=1a445a0861e'
return fetch(finalUrl)
        .then(res= > {
            if (res.status >= 200 && res.status < 300) {
                return res.json();
            }
            return Promise.reject(new Error(res.status)); })}Copy the code

The corresponding action action:

export const fetchContents = (source) = > {
    const url = '... ';
    return (dispatch) = > {
        dispatch({type: FETCH_CONTENTS_START});
        if (sessionStorage.getItem(source)) {
            console.log('get from sessionStorage');
            let articles = JSON.parse(sessionStorage.getItem(source));
            dispatch({type: FETCH_CONTENTS_SUCCESS, contents: Object.assign(articles, {currentChanel: source.toUpperCase()})})
        }
        else {
            sendByGet({url}, dispatch)
            .then((json) = > {
                if (json.status === 'ok') {
                    sessionStorage.setItem(source, JSON.stringify(json.articles)); 
                    return dispatch({type: FETCH_CONTENTS_SUCCESS, contents: Object.assign(json.articles, {currentChanel: source.toUpperCase()})})
                }
                return Promise.reject(new Error('FETCH_CONTENTS_SUCCESS failure'));
            })
            .catch((error) = > {
                return Promise.reject(error)
            })
        }
    }
}Copy the code

Request to optimize

As we know, the access speed of these asynchronous requests is slow. Therefore, I used several methods to optimize.

  • The first method is loading beautification. I used picture placeholders from the web. When I artificially simulate the network environment in the console as 3G, the page effect is as follows: (please wait for the GIF image to load)
Load occupy graph

Forgive me for using a loading image of a pink girl…

  • The second method is actually a trick. My global image is set to 0 in the initial state, and a fadeIn effect is set when the onload event is triggered:

    <img ref="image" src={imgSrc} onLoad=
    {this.handleImageLoaded.bind(this)}/>
    
    handleImageLoaded() {
        this.refs['image'].style.opacity = 1;
    }Copy the code

This tip comes from Facebook’s research into user experience. If you’re interested, you can find out about it in another of my articles.

  • As the headlines are updated from time to time, this time interval may be longer. And I thought about the fact that users are using the Web APP in fragmented time. So I used sessionStorage to cache content. Don’t ask me why I don’t use localStorage… , if you have questions, it is recommended that the features of Web Storage go back to repair it.

This is done by determining whether sessionStorage already has data for the news media (such as BBC) when sending the request. Use caching if it exists. Otherwise, the AJAX request is made and the cache is planted in the successful callback function. The code part is as follows:

if (sessionStorage.getItem(source)) {
    console.log('get from sessionStorage');
    let articles = JSON.parse(sessionStorage.getItem(source));
    dispatch({type: FETCH_CONTENTS_SUCCESS, contents: Object.assign(articles, {currentChanel: source.toUpperCase()})})
}
else {
    sendByGet({url}, dispatch)
    .then((json) = > {
        if (json.status === 'ok') {
            sessionStorage.setItem(source, JSON.stringify(json.articles)); 
            return dispatch({type: FETCH_CONTENTS_SUCCESS, contents: Object.assign(json.articles, {currentChanel: source.toUpperCase()})})
        }
        return Promise.reject(new Error('FETCH_CONTENTS_SUCCESS failure'));
    })
    .catch((error) = > {
        return Promise.reject(error)
    })
}Copy the code

Of course, if you have a plant cache, you have to have a clear cache. This button I set to the far right of the navBar component in:

const CLEAR_SESSIONSTORAGE = 'CLEAR_SESSIONSTORAGE';
export const refresh = (a)= > {
    sessionStorage.clear();
    return dispatch= > dispatch({type: CLEAR_SESSIONSTORAGE});
}Copy the code

Other details

I used the latest version of Node for the need to use advanced build tools. However, due to the business needs, it is necessary to keep the node environment of a lower version. To do this, I use the: n tool for Node version management.

At the same time, I used a series of powerful development and build features of webPack. Including but not limited to:

  • Hot update
  • Less compiled plug-in
  • Server build, using port 8088
  • JSX, es6 compilation
  • Packaging releases
  • Color log

. Wait, but I’m no Webpack expert. At Wolf Factory, of course, more FIS build tools are used. As for the comparison between FIS and Webpack, my online celebrity colleague @Yan Dashen has explored it.

conclusion

This article covers the cutting edge of the front-end development stack. It includes React framework, Redux data flow framework, functional programming, asynchronous Action middleware, Fetch asynchronous request, Webpack configuration and so on. Also invisibly involved in some mature product design ideas. Of course, the project is far from mature. In the code repository, I’m constantly updating. I hope this article will inspire you in all dimensions. Also entreats industry big cattle not grudgingly to give advice, undertake axifications.

Finally, I want to talk about some feelings about frameworks and front-end learning. I remember when I first started working on the front end, I was developing hybrid mobile apps using ionic, the Angular framework and phoneGap. I was totally confused at the time, just felt the Belgian colleague used super high, 6 to fly. Every time he explained it to me in French with a thick Belgian accent, I couldn’t understand it.

Now think at that time so the reason or lies in his JS foundation is not firm enough. When you’re at a loss to rapidly update the front-end technology, the only shortcut is to start from the basics, from the JS prototype prototype chain, this, the context of the execution environment, etc.

Readers who feel lack of front-end knowledge are welcome to follow me. Recently, I will take you to “reread” classic JS books and refine knowledge points in the form of code demo, which will be synchronized to the blog and personal Github.

Happying code!