Tách logic và UI trong React Native. Bạn đã thử chưa?

Tách logic và UI trong React Native. Bạn đã thử chưa?

Thường khi code với 1 màn hình ngoài giao diện trong phần Render thì sẽ có rất nhiều các logic khác ví dụ get data từ server các bạn sẽ để lẫn lộn trong đó. Việc đó là rất khó tránh khỏi. Hôm nay mình sẽ nói về việc vì sao nên tách logic và UI ra.
Đầu tiên mình sẽ có 1 file để demo. Các bạn hay để ý đoạn code sau:



import React, { Component } from 'react';
import {
  Platform,
  StyleSheet,
  Text,
  View,
  ScrollView,
  TextInput,
  TouchableOpacity,
  Alert,
  FlatList
} from 'react-native';
import firebase from 'react-native-firebase';
import Todo from './src/component/Todo';

export default class ListTask extends Component {
  constructor(props) {
    super(props);
    this.ref = firebase.firestore().collection('todos');
    this.unsubcribe = null;
    super();
    this.state = {
      textInput: '',
      loading: true,
      todos: [],
    }
  }

  componentDidMount() {
    this.unsubcribe = this.ref.onSnapshot(this.onCollectionUpdate)
  }

  onCollectionUpdate = (querySnapshot) => {
    const todos = [];
    querySnapshot.forEach((doc) => {
      const { title, complete } = doc.data();
      todos.push({
        key: doc.id,
        doc,
        title,
        complete,
      })

      this.setState({
        todos,
        loading: false
      });
    });
  }

  componentWillUnmount() {
    this.unsubcribe();
  }

  updateTextInput(value) {
    this.setState({ textInput: value });
  }

  addTodo() {
    this.ref.add({
      title: this.state.textInput,
      complete: false,
    });

    this.setState({
      textInput: '',
    })
  }
  render() {
    return (
      <View style={styles.container}>
        <Text style={styles.title}>My Tasks</Text>
        <FlatList
          data={this.state.todos}
          renderItem={({ item }) => <Todo {...item} />}
          style={styles.flatList}
        />
        <View style={styles.viewInput}>
          <TextInput
            placeholder={'Add TODO'}
            value={this.state.textInput}
            onChangeText={(text) => this.updateTextInput(text)}
            style={styles.textInput}
          />
          <TouchableOpacity
            disabled={!this.state.textInput.length}
            onPress={() => this.addTodo()}
            style={styles.buttonAddNewTask}
          >
            <Text style={styles.titleButton}>Add new task</Text>
          </TouchableOpacity>
        </View>
      </View>
    );
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#F5FCFF',
  },
  flatList: {
    margin: 10,
  },
  textInput: {
    height: 40
  },
  title: {
    marginTop: 50,
    fontWeight: '500',
    fontSize: 25,
    alignSelf: 'center',
  },
  viewInput: {
    shadowColor: 'black',
    shadowOffset: {
      width: 0,
      height: 3,
    },
    shadowRadius: 2,
    shadowOpacity: 0.2,
    marginTop: 10,
    borderTopWidth: 0.5,
    marginBottom: 20,
    paddingLeft: 10,
    paddingRight: 10
  },
  titleButton: {
    fontWeight: '500',
    fontSize: 20,
    color: 'white'
  },
  buttonAddNewTask: {
    backgroundColor: '#4286F3',
    height: 40,
    borderRadius: 20,
    justifyContent: 'center',
    alignItems: 'center',
    width: '80%',
    alignSelf: 'center',
    marginTop: 8
  }
});

Đây là một component điển hình trong React Native mà trước đó mình thường code. Nghĩa là thường để chung phần xử lý logic lấy data với phần render UI.
Cách viết như thế này sẽ có vấn đề sau:
– Thứ nhất: Với một màn hình thường sẽ có rất nhiều function để xử lý logic về dữ liệu và cả logic về UI. Với mình mình sẽ tách function rõ ràng nhất có thể nên có component mình viết rất nhiều function. Lấy ví dụ các Screen có nhiều TextInput các bạn sẽ có những function bắt sự kiện change text và cả validate nữa.
– Thứ hai: Viết như thế này chúng ta sẽ import tùm lum. Cái thì để gọi data, cái thì gọi các UI khác. Thử tưởng tượng thôi cũng thấy rối rồi. Lúc cần thì lại phải dò và mất 1 khoảng thời gian nghĩ xem đoạn nào import UI đoạn nào import logic

Đấy là hai vấn đề mình thường thấy.
Còn giờ mình sẽ nói về việc mình tách code:
Ý tưởng của mình là với mình màn hình có xử lý logic thì nên tách thành 2 files: index.js và render.js nó sẽ trông như thế này:

Đó các bạn thấy không.
Khi import component đó ở trỗ khác nó sẽ như thế này:

import ListTask from './ListTask'

và tự động nó sẽ nhảy vào file index.js

File index.js ban đầu nó sẽ trông như thế này:


import React, { Component } from 'react';
import firebase from 'react-native-firebase';
import { RenderListTask } from './render';

export default class ListTask extends Component {
  constructor(props) {
    super(props);
    this.ref = firebase.firestore().collection('todos');
    this.unsubcribe = null;
    this.state = {
      textInput: '',
      loading: true,
      todos: [],
    }
  }

  componentDidMount() {
    this.unsubcribe = this.ref.onSnapshot(this.onCollectionUpdate)
  }

  onCollectionUpdate = (querySnapshot) => {
    const todos = [];
    querySnapshot.forEach((doc) => {
      const { title, complete } = doc.data();
      todos.push({
        key: doc.id,
        doc,
        title,
        complete,
      })

      this.setState({
        todos,
        loading: false
      });
    });
  }

  componentWillUnmount() {
    this.unsubcribe();
  }

  updateTextInput(value) {
    this.setState({ textInput: value });
  }

  addTodo = () => {
    this.ref.add({
      title: this.state.textInput,
      complete: false,
    });

    this.setState({
      textInput: '',
    })
  }
  render() {
    const { todos, textInput} = this.state;
    return (
      <RenderListTask 
        todos={todos}
        textInput={textInput}
        updateTextInput={(text) =>this.updateTextInput(text)}
        addTodo={this.addTodo}      
      />
    );
  }
}

Và đây là render:


import React from 'react';
import {
    Platform,
    StyleSheet,
    Text,
    View,
    ScrollView,
    TextInput,
    TouchableOpacity,
    Alert,
    FlatList
} from 'react-native';
import Todo from '../../component/Todo';

export const RenderListTask = ({
    todos = [],
    textInput = '',
    updateTextInput = () => {},
    addTodo = () => {},

}) => {
    return (
        <View style={styles.container}>
            <Text style={styles.title}>My Tasks</Text>
            <FlatList
                data={todos}
                renderItem={({ item }) => <Todo {...item} />}
                style={styles.flatList}
            />
            <View style={styles.viewInput}>
                <TextInput
                    placeholder={'Add TODO'}
                    value={textInput}
                    onChangeText={(text) => updateTextInput(text)}
                    style={styles.textInput}
                />
                <TouchableOpacity
                    disabled={!textInput.length}
                    onPress={() => addTodo()}
                    style={styles.buttonAddNewTask}
                >
                    <Text style={styles.titleButton}>Add new task</Text>
                </TouchableOpacity>
            </View>
        </View>
    );
}

const styles = StyleSheet.create({
    container: {
      flex: 1,
      backgroundColor: '#F5FCFF',
    },
    flatList: {
      margin: 10,
    },
    textInput: {
      height: 40
    },
    title: {
      marginTop: 50,
      fontWeight: '500',
      fontSize: 25,
      alignSelf: 'center',
    },
    viewInput: {
      shadowColor: 'black',
      shadowOffset: {
        width: 0,
        height: 3,
      },
      shadowRadius: 2,
      shadowOpacity: 0.2,
      marginTop: 10,
      borderTopWidth: 0.5,
      marginBottom: 20,
      paddingLeft: 10,
      paddingRight: 10
    },
    titleButton: {
      fontWeight: '500',
      fontSize: 20,
      color: 'white'
    },
    buttonAddNewTask: {
      backgroundColor: '#4286F3',
      height: 40,
      borderRadius: 20,
      justifyContent: 'center',
      alignItems: 'center',
      width: '80%',
      alignSelf: 'center',
      marginTop: 8
    }
  });

Các bạn thấy khác biệt không:
– Đầu tiên là việc import:
+ Vì index.js chỉ xử lý các vấn đề về logic nên nó chỉ import các hàm cần thiết như firebase.
+ Còn render.js thì import các component.
– Thứ hai: Code rất tách biệt. Cần sửa code logic thì vào index. Sửa code UI thì vào render. Clear vãi lúa.
– Thứ ba: phần reder các bạn có thể default sẵn params.
– Thứ tư: Mình thật sự nghĩ việc gọi state hay props các bạn nên gọi 1 chỗ như thế này:


    const { todos, textInput} = this.state;
    const { props1, props2 } = this.props;

Nó sẽ không phải duplicate đoạn this.state hay this.props.
Và cũng nhìn kết quả nhé:

Đây là link github các bạn có thể clone về chạy thử:
Repo Demo

Vậy khi nào bạn nên viết như thế này:
– Khi nó là một Screen có xử lý nhiều logic. Các component nhỏ có thể không cần.
– Bạn muốn tách bạch code.
– Các dự án mới còn các dự án đang phát triển thì mình nghĩ không nên thay đổi code khi nó đang chạy ngon
Nếu bạn thấy bài viết hữu ích thì Like và Share nhé. Thank.

Leave a Reply

Close Menu