The importance of fundamentals in the learning process was mentioned in the summary section of an article the other day. Of course, learning new frameworks is not a bad idea. In this article, we will share an example of the React + Redux project.

I have been studying react.js and sharing it several times. I also used this technology stack in the company’s product private message project that I participated in. During the process of learning React + Redux, I felt that there were not many initial demos, and the community was mostly using the broken Todolist tutorial, which was rather boring.

React + Redux React + Redux React + Redux react+ Redux This is the first of a series of tutorials that doesn’t cover redux middleware, redux handling async, react performance optimization, immutable data immutable. However, the content not covered will be improved step by step with the complexity of this demo, which will be analyzed and used in subsequent chapters.

The entire project code can be found on GitHub.

Introduction to the

Baidu Experience Personal Center (WAP terminal) is a page group with a lot of experience traffic, among which the personal customization page is one of the important pages, please click here to view the effect with your mobile phone, the screenshot of the page is as follows:





Page screenshots

Functions are clear at a glance. It is mainly divided into two parts: 1) You can choose your favorite experience category items in the “Select category” block to subscribe. In this block, we can switch categories by clicking the “Swap” button. 2) The selected results will be displayed in the “Selected categories” block. In the “Selected categories” section, we can click on the relevant experience category item to unsubscribe.

In the current online version, we use the traditional DOM manipulation (zepto library) to implement these interactions. React is a whole new idea. Which ones are better and which ones are worse can be summed up at the end. All right, without further ado, let’s get down to business.

architecture

In keeping with our online code, I used FISP for engineering organization. Most of the community is now using WebPack, in fact, these tools are the same, solve similar problems, not to expand here. Even if you don’t understand FIS, that doesn’t stop you from reading.

App/app folder

Action. Es defines all actions for dispatch in page interaction; App.jsx is the page entry script; Component.jsx defines the components of the page; Reducer.jsx receives the actions, which defines all the reducers used.

In the lib + lib-nomod folder

These two folders are the source of the framework we will use, such as react.js+redux.js, etc. This project uses the uncompressed version of Act15.3.1, which is more stable. The reason for using the uncompressed version of React Addons is to use perf, which will be analyzed in the performance tuning section later. We know react and Redux exist independently, and we use the popular react-Redux.js library to connect the two.

Other relevant documents

Fis-conf.js is used for fIS configuration, such as packaging rules, publishing rules, compile configuration, etc. At the same time, we configured Babel to compile ES6 and JSX, and autoprefixer; Server. conf is an attached file to fIS for data mocks; Build. sh and BCLOUD are live scripts. Here we will learn how to use react.

The page data

The back-end of our department is PHP, using Smarty template. This page synchronizes the request with some data, such as user information, which is output in the template. Our sync data looks like a screenshot (the data has been streamlined, renamed, and redesigned for confidentiality reasons) :





Page synchronization data

We care about selectList and likedList: 1) The likedList lists the subscription categories that the current user has selected; 2) The selectList lists all the optional categories, ranging from 1 to 127,127. The data format is shown above.

The specific implementation

With that said, it’s time to get down to code. If you don’t understand the above, it doesn’t matter. Because it involves some organizational aspects of the project. The following content is the specific code analysis.

Data design

The idea behind the React + Redux development front end is that pages are data-driven. It has been analyzed that our page mainly consists of two kinds of data: 1) One is selectList, let’s call it selection pool data; 2) The other is likedList, which we call selected data. These two pieces of data were initially received by the Reducer and set to the initial state of the container component, which was passed to the corresponding display component by the container component.

Component design

Component design is shown in the screenshot below:





Component design

According to the React-Redux idea, components can be divided into: 1) container components, which are responsible for receiving data; 2) The display (puppet) component is responsible for receiving data up and presenting component UI according to the data.

Obviously, we have two main presentation components called: 1) SelectedBlock, which displays selected and subscribed content; 2) SelectListBlock, which shows the contents of the page selection pool available for selection. Together they are encased in a parent component called DemoApp.

The SelectedBlock component needs to care about the selected data likedList; 2) SelectListBlock: Select pool data and selected data. You might ask, “Isn’t SelectListBlock enough to care about the selection pool data?” However, the product manager requires that in the selection pool, the styles be grayed when the user’s selected items are rendered, and that no action is triggered when a selected category item is clicked. So the selection pool SelectListBlock component also relies on selected data to make changes accordingly.

These two items of data are distributed to the container component by React-Redux, and to the display component by the container component as needed.

With that in mind, let’s look at the entire code of the outermost DemoApp component:

class DemoApp extends React.Component {
    constructor(props) {
        super(props);
    }
    render() {
        const { dispatch } = this.props;
        return (
            <div>
                <SelectedBlock 
                    likedList={this.props.likedList} 
                    onDeleteLikeItem={(item)= >{dispatch(action.deleteLikeItem(item))}}>
                </SelectedBlock>

                <SelectListBlock 
                    selectList={this.props.selectList} 
                    likedList={this.props.likedList} 
                    onAddLikeItem={(index, item) = >{dispatch(action.addLikeItem(index, item))}}>
                </SelectListBlock>
            </div>)}}Copy the code

If we look at his Render () section, it’s clear that he nested the SelectedBlock component in parallel and passed the likedList data to it as a property; 2) Include SelectListBlock and pass selectList and likedList data as properties.

So the SelectedBlock design looks like this:

class SelectedBlock extends React.Component {
    constructor(props) {
        super(props);
    }
    deleteItem(event, index) {
        this.props.onDeleteLikeItem(index);
    }
    render() {
        let likedList = this.props.likedList;
        let likedListArray = [];
        let likedListKey = Object.keys(likedList);
        likedListKey.forEach(function(index){
            likedListArray.push(likedList[index]);
        })
        return (
            <div>
                <h2>Selected categories (<em id="f-num">{likedListArray.length}</em>)</h2>
                <div className="selected-list" style={{overflow: 'auto'}} >
                    <ul className="feed-list">
                        {
                            likedListArray.length > 0 ?
                            likedListArray.map((item, index) => {
                                return (
                                    <li style={{position: 'relative'}} >
                                        <span>{item}</span>
                                        <a style={deleteIconStyle} 
                                            onClick={event= >{this.deleteItem(event, likedListKey[index])}}>
                                        </a>
                                    </li>)}) :<li className="empty-list">There are no subscriptions yet<br />Please select subscribe below</li>
                        }
                    </ul>
                </div>
            </div>)}}Copy the code

We converted the likedList to the likedListArray array, in render(), using the map loop; When the user deletes an item, it fires the deleteItem(Event, index) method, which is passed up and fires the corresponding action in the parent component of DemoApp. This deletion is not a pure intra-component action, because the action will affect the SelectListBlock component by making changes to the selected data. Therefore, a series of logic needs to be processed in reducer. After processing, the selected data is reset and the page is updated.

The SelectListBlock component is also well understood:

class SelectListBlock extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            flag: 0
        }
    }
    onChangeGroup(event) {
        event.stopPropagation();
        let flagNow = this.state.flag;
        if (flagNow == 117) {
            this.setState({
                flag: 0
            });
        }
        else {
            this.setState({
                flag: flagNow + 9
            });
        }
    }
    onSelectItem(index, item) {
        var likedList = this.props.likedList;
        var likedListKey = Object.keys(likedList);
        if ( likedListKey.indexOf(index.toString()) >= 0 ) {
            return;
        }
        this.props.onAddLikeItem(index, item);
    }
    render() {
        let selectListArray = [];
        for (var i in this.props.selectList) {
            selectListArray.push(this.props.selectList[i])
        }
        let likedList = this.props.likedList;
        let likedListKey = Object.keys(likedList);
        return (
            <div>
                <h2 className="clr">
                    <span onClick={event=>{this.onChangeGroup(event)}}> Change </span> select category </h2> <ul className="feed-list clr">
                    {
                        selectListArray.slice(this.state.flag, this.state.flag+9).map((item, index)=>{
                            return (
                                <li onClick={event=>{this.onSelectItem((index + this.state.flag), item)}} 
                                    key={index + this.state.flag}>
                                    {(likedListKey.indexOf((index + this.state.flag).toString()) >= 0 ?
                                        <span className='disable'>{item}</span>
                                        :
                                        <span>{item}</span>
                                    )}
                                </li>
                            )
                        })
                    }
                </ul>
            </div>
        )
    }
}Copy the code

We start by converting the 127 optional data calls from the back end into an array of selectListArray. Each of the 127 categories corresponds to an index (1-127). In render(), since the page only shows nine options at a time, we slice the selectListArray with nine outputs sequentially. Clicking the swap button triggers the onChangeGroup () method, which is an intra-component method that takes the slice parameter +9 and reverts it back to 0 when it reaches 127.

As we know, when the onChangeGroup method triggered by clicking “change” changes the flag, since the flag is the internal state of the component, its change will cause the component to rerender (), so the datapool will switch without pressure.

At the same time, we bind the onSelectItem method to each category in the datapool, which is passed up to the parent component, which issues the corresponding action. Because this action changes the selected data, it affects the parallel SelectedBlock component. Therefore, it needs to be processed in the Reducer.

The action design

Given the design of the above components, it is clear that we need to define two actions: 1) The first is to add an item to the selected category

export const ADD_LIKE_ITEM = 'ADD_LIKE_ITEM';Copy the code

The corresponding action creator:

export function addLikeItem (index, item) {
    return {
        type: ADD_LIKE_ITEM,
        obj: {
            index: index,
            item: item
        }
    }
}Copy the code

Return an action object with type named ADD_LIKE_ITEM and load data: item name item and its index.

2) The other is to delete an entry in the selected category:

export const DELETE_LIKE_ITEM = 'DELETE_LIKE_ITEM';Copy the code

The corresponding action creator:

export function deleteLikeItem (index) {
    return {
        type: DELETE_LIKE_ITEM,
        index
    }
}Copy the code

Returns an Action object, including type and load data. At this point, the action script only needs to define actions and no further processing is required. All action processing is accepted by the Reducer.

Reducer design

Reducer is a pure function that accepts two parameters, state and action. Return a new state for the action, causing the component that subscribed to the state to render () again; We set the synchronization template data initialLikeBlockState to the initial state:

var initialLikeBlockState = F.context('likedList');
function likeBlockReducer (state = initialLikeBlockState, action) {
    switch (action.type) {
        case actionType.ADD_LIKE_ITEM: {
            var addIndex = action.obj.index;
            var newLikedList = Object.assign({}, state, {
                [addIndex]: action.obj.item
            })
            return newLikedList;
        }
        case actionType.DELETE_LIKE_ITEM: {
            var newLikedList =  {};
            for (var key in state) {  
                var val = state[key];  
                newLikedList[key] = val;  
            }  
            var index = action.index;
            delete newLikedList[index];
            return newLikedList;
        }
        default: {
            returnstate; }}}Copy the code

When matching the ADD_LIKE_ITEM action, we merge the current state with the data (item, index) brought by the action, thereby returning a new selected data state, that is, adding the state of the new category item. When matching the DELETE_LIKE_ITEM action, we remove the index that the action payload brings to the item to be deleted. Returns the new state after the entry was deleted.

conclusion

So far, we have introduced the basic design and development ideas. The tutorial contains almost all the code.

Compare the existing code on the line

1) Compared with zepto’s implementation online, react has a distinct advantage in code volume. 2) In terms of development ideas, it is a question of radish and green vegetables. But for me, who is used to writing $(), this new approach to development came as a big surprise. 3) To implement this logic online, we may need to select many DOM elements for processing for a simple UI interaction. Overall, it is complex and messy and not easy to maintain.

The next…

Of course, this is only the first step. There is more to come. For example: 1) When we select or delete an item, how to send an asynchronous request to the back end is not mentioned. Therefore, the Redux asynchronous process is not presented. This will be explained in subsequent chapters. 2) We do not use immutable.js for simplicity in this chapter because our data is delivered synchronously from back-end templates, and the data volume is not large and the structure is not complex. Of course, this will be covered in subsequent chapters. 3) I didn’t cover using the Redux Dev Tool, which is a really nice tool. Especially when the data is complex, for debugging can help greatly. I will describe the use of this tool separately later. 4) Finally, such a simple interaction does not involve page performance. In the following chapters, I will construct extreme cases for some edge testing and use some methods for performance optimization in conjunction with chrome Dev Tool. Stay tuned.