Recently, I started learning React. In order to make the learning process more interesting, I chose a simple and fun board game gem Merchant to develop. Create-react-app + React-redux + React-router-dom + ANTD is the most basic technology stack. The design of the board game interface is mainly based on wow Board Game, but it is a standalone version, so there are some improvements. I want to share some interesting board games and my own design, and record some problems encountered in the development, including the following:

  1. Game flow & interface component design
  2. Data design react-Redux
  3. React data state storage methods and differences
  4. Why is the latest data not available in setInterval
  5. Problem with setting key={index}
  6. .

So far think of so much, suddenly remember what to add to it


1. Game flow & interface component design

The general process of gem merchant is that 2 players (there can be more than one but I only made 2) take turns to exchange gems or cards, and the one who collects 15 points first wins.

  • There are two ways to get points: one is to exchange cards with points (the score is indicated on the upper right corner of the card), and the other is to get favors from aristocrats (you can get favors if you meet the number of cards required by aristocrats).
  • You can choose 3 operations per turn. (1) You can only take white, blue, green, red and brown stones. At most 3 stones of different colors and at most 2 stones of the same color. (2) Reservation card, this card can not be stolen by another player after reservation, and will be given a yellow gem (can be used as any color), the next return can choose to exchange reservation card, there are only 5 yellow gems, so 2 players can reserve a maximum of 5 cards; (3) The card can be exchanged if the color of the corresponding card is satisfied.
  • Redeemable cards have colors that are equivalent to permanent gems of the same color.
  • The content selected by the user is displayed in real time. After the user confirms the selection, the content is submitted to the user information for saving.

According to the above game flow, the interface and components can be designed

The Game interface to a parent component, including User component is used to represent the User, according to the current User points, the Card number, number of gems, appointment CARDS, and the countdown, Card component is used to represent the middle Card, can choose the Noble components required Card shows Noble favor, Gem component is used to display the current remaining number of precious stones, The Round component is used to display the content selected by the current Round user.

2. Data design react-redux

  • Q1: Why redux

Actually at first I was considering the all data on the parent component directly to the Game, and then through the props is passed to the child component data and modify the data, the method of this component can get all the data and can modify the data, but the main problem is that this data is not convenient to modify and management standardization, child components modified data is very easy to get wrong. Therefore, finally, react-Redux was selected for data management, and methods for modifying each data were defined in reducer, so that data could be standardized and modified, and there would be fewer problems in development. I don’t know if this understanding is correct

  • Q2: Do one Action and Reducer manage one data?

When using react-redux, the tutorial focuses on defining a reducer file for each component, but I think we should define a reducer file for each data, which records the operation methods for each data. If each component has a reducer file, What if both components use the same data? I think there seems to be something wrong with the tutorial, I developed a reducer according to each data!

I. System data: The system data is the data initialized by the game. It is randomly selected from the following data. It is required to select 3 nobles and 4 first-class cards, 4 second-level cards and 4 third-level cards.

  1. What does it take for a nobles card to be nobles’ favorite? +3 points
[{blue: 4.white: 4,},/ /...
]    
Copy the code
  1. Level 1, level 2, level 3 cards, Level 1 cards 20, Level 2 cards 30, level 3 cards 40.
{
    level3: [{score: 5.color: 'blue'.needs: {
                    white: 7.blue: 3,}},/ /...].level2: [{score: 3.color: 'blue'.needs: {
                    blue: 6,}},/ /...].level1: [{score: 1.color: 'blue'.needs: {
                    red: 4,}},/ /...]}Copy the code

Ii. Data managed by Redux include:

  1. RemainGem: Stores the number of remaining gems
 { white: 5.blue: 5.green: 5.red: 5.brown: 5.gold: 5 }
Copy the code
  1. Round: Stores the current round and the current user
{
    number: 1./ / the number of turn
    userIndex: 0./ / user id
}
Copy the code
  1. UserList: stores the current user information
[{
    name: Players' 1 '.score: 0./ / score
    gem: { white: 0.blue: 0.green: 0.red: 0.brown: 0.gold: 0 }, / / gems
    card: { white: 0.blue: 0.green: 0.red: 0.brown: 0 }, / / the card
    order: [].// Reserve cards
},
{
    name: Players' 2 '.score: 0.gem: { white: 0.blue: 0.green: 0.red: 0.brown: 0.gold: 0 },
    card: { white: 0.blue: 0.green: 0.red: 0.brown: 0 },
    order: [],}]Copy the code
  1. SelectedContent: The content selected by the current session, divided into four types.

(1) The gems selected by the user

{
    type: 'gem'.content: {
        gemList: ['white'.'blue'.'yellow'],}};Copy the code

(2) The card selected by the user is the card

{
    type: 'card'.content: {
        card: {
            score: 3.needs: { red: 3.blue: 3.green: 3.white: 3}}}};Copy the code

(3) The user selects the reservation

{
    type: 'order'.content: {
        card: {
            score: 3.needs: { red: 3.blue: 3.green: 3.white: 3}},gemList: ['gold'] // Reserve a gold universal gem by default}};Copy the code

(3) The card selected by the user is the card previously reserved by the user

{
    type: 'order-card'.content: {
        card: {
            score: 3.needs: { red: 3.blue: 3.green: 3.white: 3,}}}};Copy the code

You can modify each data as follows:

data methods meaning
remainGem changeRemainGem To change the number of remaining gems, set type to +1/-1 and gemList to which gems you want to operate
round changeRound Change the current turn to the next turn
userList addUser Initialization adds one player
changeUserSelectedGem Modify the number of gems the player already has
changeUserSelectedCard Modify the number of cards a player already has
changeUserScore Modify player score
changeUserOrderList Modify player preorders
changeUserWin Change the player’s state to win the game (maybe a player wins, maybe a draw)
selectedContent addSelectedGem Adds a gem selected by the current user
reduceSelectedGem Reduces the gems selected by the current user
changeSelectedCard Change the current user’s selection to a card
changeSelectedOrder Modify the current user’s selection to reserve a card
changeSelectedOrderCard Modify the selection of the current user to a card that the user has reserved

SelectedContent is required because the selectedContent is displayed in the Round component, and the current User can modify the selectedContent until the end of the Round. Only after the end of the Round is selected will the selectedContent be submitted to the User component for display.

3. React data state storage methods and differences

In a class component, data state is stored as follows:

  1. On this instance
  2. In this. In the state
  3. Static Static attributes
class Components extends React.Component {
    this.test = 1; // Method 1: on this instance
    constructor (props) {super(props);
        this.state = {
            test: 1 // Method 2: in this.state
        }
    }
}
Components.test = 1; // Method 3: static attribute
Copy the code

The differences between these three methods are as follows:

  1. This. State stores responsive data related to views. When data related to state changes, setState updates the data, enabling React to update views.
  2. The data stored on this instance, each instance of the component has the test attribute, and each instance of the test attribute is unrelated to each other, generally store the data required by the instance, and not directly related to the view.
  3. Static properties store instance-independent data that is shared by each component instance and accessed through component name. property name.

Similarly, in a function component, data state is stored in the following four types:

  1. useState
  2. useRef
  3. Static attributes
  4. Var /let/const a variable
function Component(){
    const [test, setTest] = useState(1); 1 / / way
    const testRef = useRef(1) 2 / / way
    var test1 = 1; 3 / / way
    / /...
    return <div></div>;
}
Component.test = 1; // Method 3: static attribute
Copy the code

The differences between these four methods are as follows:

  1. Method 1 is reactive data, which is directly related to the view and needs to be changed by setTest.
  2. Method 2 is not reactive and changes the data by testref.current = 2.
  3. Method 3 is a static property that is accessed through component.test and can be shared between Component samples.
  4. The test1 variable in mode 4 is one-off. Each time a view update calls Component to fetch the latest render, a new test1 variable is redefined and assigned to 1, so test1 can only perform one-off operations.

4. Why can’t I get the latest data in setInterval

The reason I focused on 3’s data storage was because I encountered this problem during development:

  • Scenario: I use the function component for development. Since the countdown of User operations is required, I define a timer in the User component, which will change the remaining time of the User every 0.1 second, thus making the progress bar change.
  • Data: I am using useRef method to store the timer, because the timer and response type view is not directly related to, but the view is updated before the need to get to the timer, so that the timer will not repeat definition, convenient to remove the timer, and view the associated time remaining I is to use useState definition, because it is directly related to the view.
function User(props){
    const [selectRemainTime, setSelectRemainTime] = useState(120); // The remaining time
    const taskRef = useRef(null); / / timer
    if (task.current === null && props.index === props.round.userIndex) {
        // No timer is currently defined and the current user is an instance of this component
        taskRef.current = setInterval(() = > {
            setSelectRemainTime(v= > v - 1); // Update time
            // There are some conditions to clear the timer
            / /!!!!!!
            // selectRemainTime is always the initial value
            if ( selectRemainTime < 0 ) {
                clearInterval(taskRef.current);
                / /!!!!!!
                // Get the react-redux update data from props to perform some operations
                // Can not get the latest prosp}}}})Copy the code

Why is the latest selectRemainTime and props data not available in setInterval? SelectRemainTime and props are initialized, not updated, in the parent scope of the function because there is no selectRemainTime and props in the parent scope.

So the solutions include:

  1. Use useEffect to add selectRemainTime to the useEffect dependent data.
  2. Using useRef, add references to selectRemainTime and props, updating the current value each time using useRef.
  3. Use the official useCallback.

UseEffect is not used to address changes in the props, because the properties of the props are not changing, but the underlying objects are changing. UseEffect cannot be used to monitor changes in the data related to react-redux.

Finally, I solved the problem of updating selectRemainTime and props data in this way:

function User(props){
    const [selectRemainTime, setSelectRemainTime] = useState(120); // The remaining time
    const taskRef = useRef(null); / / timer
    const propsRef = useRef(props); // references to props
    propsRef.current = props; // Update the reference
    if (task.current === null && props.index === props.round.userIndex) {
        // No timer is currently defined and the current user is an instance of this component
        let count = selectRemainTime; // Form a closure
        taskRef.current = setInterval(() = > {
            setSelectRemainTime(--count); // Update the time and count variables
            if ( count < 0 ) {
                clearInterval(taskRef.current);
                // propsref.current to get the latest data}}}})Copy the code

SelectRemainTime uses closures and props uses useRef.

5. Set key={index}

Much of the data is stored as the Object type, which is written in JSX syntax

Object.keys(data).map(e= > (<div></div>))
Copy the code

In my development process, there was no error due to the lack of a key, but for a clean console ~ fix this problem!

When you compare old and new virtual DOM trees using the diff algorithm, the dom element needs to be updated based on the key. In React, the dom element needs to be updated based on the key. Index is not set by default. This will reduce the efficiency of rendering, for example, when inserting new elements, if the key is not properly set, it will lead to the update of one more element.

If the data only needs to be rendered and there is no update, then setting key={index} is fine because there is no update and no diff; If update is involved, but there is no increase, deletion or change of input components such as input, setting key={index} is also no problem, but may reduce rendering efficiency; Using key={index} is likely to cause problems if it involves adding, deleting, or modifying input components.

Data that can be used as keys:

  1. The unique value of the data, such as the ID value and so on;
  2. When the data is initialized, add an ID value, you can use a timestamp, random number, and so on.

Recently received a tencent cloud free server, deployment, and seems to be more than a dozen of April will be expired, the address is: http://42.192.152.124:5277/splendor/, don’t know now there are no bugs, I changed my several version T T