Skip to main content

[Thủ Thuật WordPress] Tạo theme Wordpress bằng React - Phần 3

Tiếp tục với chủ đề "Tạo theme Wordpress bằng React". 

Trong các theme WordPress - có “ The Loop ”, nơi nó lấy tất cả nội dung bài đăng tại một truy vấn nhất định. Đây có thể là một trang, một bài đăng hoặc một loạt các bài viết.

Tất cả đều đi đến “ The Loop ”, nơi bên trong vòng lặp đó - là các phương pháp giúp việc truy xuất dữ liệu bài đăng trở nên dễ dàng hơn.  Bên trong vòng lặp, chúng ta có thể làm một cái gì đó như “ get_the_ID () ” hoặc “ the_title () ” - sẽ xuất ra id bài đăng hoặc tiêu đề của nó.

Chúng ta cũng đang cố gắng đạt được điều tương tự trong theme React WordPress của mình. Ở đây chúng ta có thành phần “TheLoop” - thành phần mà chúng tôi sẽ thêm vào thư mục “partials” của mình.

Thành phần này sẽ được sử dụng ở mọi nơi - trong các bài đăng, trang hoặc trang lưu trữ như tìm kiếm, v.v.

Đây sẽ là bài viết cuối cùng của một loạt các bài viết:

Phần 1 - Cài đặt và tìm hiểu về theme React WordPress

Phần 2 - Xây dựng component, route và context trong theme React WordPress

Phần 3 - Vòng lặp, phân trang trong theme React WordPress

Trước đây, chúng tôi chỉ xuất slug trong TheLoop. bây giờ chúng ta sẽ sửa TheLoop như sau

import React from 'react'; 
import WithConsumer from '../context/WithConsumer';
import ThePost from './ThePost';

const TheLoop = ({ context }) => {

    const posts = () => context.posts;
    const pos = posts();
  
    let results = '';
     
    if(context.appError){
      results = <div className="app-error">{context.appError}</div>;      
    }else{
      if(pos.length === 0){
        results = <div className="no-results">no results</div>;
      }else{
        results = pos.map(function(item,i){
             return <ThePost key={i} index={i}></ThePost>
           })
      }
    }

    return (results);

};
export default WithConsumer(TheLoop);

Thực sự không có nhiều thay đổi ở đây, chỉ đơn giản là cô lập logic "vòng lặp" trong thành phần này. Vì vậy, chúng ta không cần phải làm như vậy ở những nơi khác.

Nhưng điểm mấu chốt cần chú ý là chúng ta đang nhận được kết quả từ Context của mình. Và tùy thuộc vào số lượng kết quả, nếu có nhiều hơn 0, chúng tôi - trả về thành phần “ ThePost ” (sẽ nói thêm về điều này sau).

Nếu không có kết quả - chúng tôi chỉ trả về một div với văn bản “no result”.

Nếu có lỗi - hãy trả lại thông báo lỗi.

ThePost

Thành phần ThePost có tất cả các phương thức cần thiết để xuất ra dữ liệu bài đăng. Điều này giống với “ the_post () ” của WP trong PHP.

Tại đây, chúng tôi có quyền truy cập vào context.posts, được lọc theo chỉ mục index. Chỉ mục là một hỗ trợ mà chúng ta thông qua TheLoop. Vì vậy, bây giờ, mỗi mục item là một bài:

const ThePost = ({index,context}) => {
 
    const posts = () => context.posts;
    const item = posts()[index];  
    
    let linkPrefix = item.type === 'page' ? '/page/' : '/post/';
    
    let theContent = ''; 
    
    switch(context.route){
        case '/': //if homepage,
        case '/search/:term': //or if search
        case '/category/:catid': //or if search
            theContent = item.excerpt.rendered; //show excerpt only
        break;
        default: //for single, pages - show entire content
            theContent = item.content.rendered;
        break;
    }   
 
    return (
        <div id={'post-id-'+item.id} className={'post-item'}>
            <h1><Link to={linkPrefix+item.slug}>{item.title.rendered}</Link></h1>
            <PostMeta index={index}></PostMeta>
            <div className="post-content" dangerouslySetInnerHTML={{__html:theContent}}></div>
        </div>);
 
};

Chúng tôi phải xác định xem đó là “bài đăng” hay “trang” (dòng 6). Điều này sẽ khớp với route bài đăng / trang trong index.js của chúng tôi .

Sau đó, chúng tôi có một câu lệnh chuyển đổi, kiểm tra route. Nếu nó là một trang lưu trữ (archive) (như tìm kiếm, danh mục) - hãy chỉ xuất phần trích dẫn nội dung. Nếu không - nếu nó là một, hãy xuất toàn bộ.

Thành phần PostMeta cũng là một thành phần mới mà chúng tôi chưa xây dựng. Hãy thêm điều đó một chút. Bây giờ, hãy lấy dữ liệu.

Context.js

Bây giờ có một số điều chúng ta cần làm với thành phần Context để vòng lặp hoạt động. Hãy nhớ trong vòng lặp của chúng ta, chúng ta đã thiết lập các liên kết bằng cách sử dụng “Link to”.

Chúng tôi cần ánh xạ (map) các liên kết này tới entrypoint url REST bên phải - để chúng tôi có thể lấy đúng dữ liệu và đưa nó vào đúng kho lưu trữ dữ liệu.

Hãy nhớ rằng, tất cả dữ liệu đều nằm trong một file này. Có thể đó là các bài viết, bình luận - thậm chí cả các phương thức thao tác dữ liệu này cũng nằm trong thành phần này. Vì vậy, tập tin này vô cùng quan trọng.

Hãy bắt đầu bằng cách thêm các vùng chứa cho trạng thái của chúng ta. Trong hàm tạo, hãy thêm những thứ sau:

constructor(props) {
    super(props); 
 
    let restType = this.getRestType(props.router.match.path);
    let route = props.router.match.path;
    let slug = props.router.match.params.slug ? props.router.match.params.slug : '';      
 
    this.state = {    
      slug : slug,
      restType : restType,     
      route : route,
      posts : []     
    };
 
  }

State của chúng ta hiện có các vùng chứa cần thiết cho dữ liệu chúng ta. Chủ yếu mảng “posts” là thứ chúng tôi muốn lấp đầy. Hãy thêm một số hành động trong lifecycle hook của chúng ta: componentDidMount:

componentDidMount(){   
    this.getPosts(this.buildUrl());      
}

Xây dựng một vài hàm - một hàm để tạo entrypoint url REST và hàm kia để tìm nạp các bài đăng:

buildUrl(){
    let url = '/wp-json/wp/v2/';    
    switch(this.state.restType){      
      case 'page': 
        url += 'pages/?slug=' + this.state.slug
      break;     
      case 'post': 
      default:      
        url += 'posts/?slug=' + this.state.slug ;
        break;      
    }
 
    return url;
  }
 
 getPosts (url){
    let self = this;
    Axios.get(url).then((response)=>{
      self.setState({
        posts : response.data        
    }).catch(function(error){
      console.log(error);
      self.appError = 'An unexpected error occurred';
    });  
  }
 

Điều đó sẽ giúp ta lấy được dữ liệu bài đăng và trang mang lại từ API. Đảm bảo rằng TheLoop nằm trong các thành phần Archive và Single của bạn.

Và nếu tất cả đều tốt, chúng ta sẽ có một cái gì đó như tương tự dưới đây:

Image
Hướng dẫn tạo theme React WordPress - post-page

Hãy nhớ rằng chúng ta phải thay đổi liên kết cố định trong WordPress để phù hợp với lộ trình của chúng tôi.

Add Paging

Phân trang là cần thiết đặc biệt đối với các trang Archive. Bạn chỉ muốn xem X số lượng bài đăng mới nhất, sau đó hiển thị X tiếp theo - v.v.

Trong Archive.js - chỉ cần thêm pager component (mà chúng ta vẫn phải xây dựng), ngay bên dưới vòng lặp:

<TheLoop></TheLoop>
 <Pager></Pager>

Bây giờ chúng ta hãy tạo một file trong thư mục partials của chúng ta - gọi nó là Pager.js .

import React, { useEffect } from 'react'; 
import WithConsumer from '../context/WithConsumer';
 
const Pager = function ({context}){
 
    let prevBtn =  React.createRef(); 
    let nextBtn =  React.createRef(); 
    let curPage = () => context.currentPage;
    let totalPages = () => context.totalPages; 
 
    useEffect(() => {
        prevBtn.current.disabled = true;
        // eslint-disable-next-line react-hooks/exhaustive-deps
    },[]); 
    
    function nextClicked(){
 
        context.nextClicked(); 
 
        if(parseInt(totalPages()) === parseInt(curPage() + 1) ){
            nextBtn.current.disabled = true;
        }
 
        prevBtn.current.disabled = false;
 
    }
 
    function previousClicked(){
 
        context.previousClicked(); 
 
        if(parseInt(curPage()-1) === 1 ){
            prevBtn.current.disabled = true;
        }
 
        nextBtn.current.disabled = false;
 
    }
 
    function pagerClass(){ 
        let cls = 'Pager'; 
        if(parseInt(totalPages()) <=1 || 
            context.appError){
            cls = 'Pager hidden';
        }
        return cls;
    }
 
 
    return (
        <div className={pagerClass()}>
        <button ref={prevBtn} onClick={previousClicked}>Previous</button>
        <button ref={nextBtn} onClick={nextClicked}>Next</button>
        <div className="PagerText">Page 
            <span dangerouslySetInnerHTML={{__html: curPage()}}></span> of 
            <span dangerouslySetInnerHTML={{__html: totalPages()}}></span></div>
        </div>
    )
}
 
export default WithConsumer(Pager);

Bây giờ logic pager có liên quan nhiều hơn một chút. Ở trên chỉ xuất ra các nút “Next” và “Previous” - tùy thuộc vào số lượng trang.

May mắn thay, API của WordPress làm cho việc này trở nên dễ dàng với chúng ta. Chúng ta có 2 thuộc tính tiêu đề quan trọng từ mỗi phản hồi (response) được gọi là "X-WP-Total" và "X-WP-TotalPages".

Từ đó, chúng tôi có thể xác định logic cần thiết cho phân trang của chúng tôi.

Quay trở lại context, chúng ta cần sửa đổi state của mình, thêm một số phương thức sửa đổi state đó, cũng như sửa đổi các lệnh gọi còn lại của chúng ta.

//ADD THIS TO this.state IN THE CONSTRUCTOR
 
currentPage : 1, 
totalPages : 0,    
nextClicked : this.nextClicked.bind(this), 
previousClicked : this.previousClicked.bind(this), 

Trên đây cho thấy state mới của chúng ta.

Lưu ý rằng currentPage luôn bắt đầu ở 1.

Totalpages - chúng tôi cập nhật mỗi lần gọi, sau đó chúng tôi liên kết 2 phương thức với “this”.

Tiếp theo, hãy thêm sửa đổi phương thức buildUrl () của chúng ta để có các tham số trang từ state của mình.

 

case 'post': 
      default:      
        url += this.state.slug ? 'posts/?slug=' + this.state.slug : 'posts/?page=' + this.state.currentPage;
        break;    

Ở trên chỉ cần nối trang hiện tại với entrypoint url. Trong phương thức getPosts () của chúng tôi, chúng tôi cập nhật state TotalPages của mình với mỗi response:

Axios.get(url).then((response)=>{
      self.setState({
        posts : response.data, 
        totalPages : response.headers['x-wp-totalpages']
      }

Bây giờ tất cả những gì chúng ta cần làm là tạo hai hàm - tương ứng với nút nào đã được nhấp:

nextClicked (){ 
    let newPage = this.state.currentPage + 1;    
    this.setState({
      currentPage : newPage
    },function(){
      this.getPosts(this.buildUrl());
    })   
  }
 
 
  previousClicked (){
    let newPage = this.state.currentPage - 1;    
    this.setState({
      currentPage : newPage
    },function(){
      this.getPosts(this.buildUrl());
    })
  }

Cả hai phương thức này đều được gọi từ component Pager của chúng tôi - component mà chúng tôi truy cập bằng cách thực hiện context.XXXClicked () . 

Chúng tôi cũng để giao diện người dùng quyết định hiển thị hay ẩn các nút tương ứng. Vì vậy, nếu mọi việc suôn sẻ, chúng tôi có một cái gì đó như dưới đây:

Image
Hướng dẫn tạo theme React WordPress - paging

Có lẽ đến đây bạn cũng đã hiểu cách hoạt động của theme React WordPress như thế nào, còn lại là tùy thuộc vào bạn.

Lưu ý rằng có rất nhiều đoạn code bị bỏ qua ở trên. Chỉ các bước quan trọng được nêu ra - phần còn lại là tùy thuộc vào bạn.

Link Full Source code

 

Xem tiếp : Tạo theme Wordpress bằng React - Phần 1

Xem tiếp : Tạo theme Wordpress bằng React - Phần 2